- * The Private URL feature has been implemented as a specialized role assignment
+ * The Preview (formerly Private) URL feature has been implemented as a specialized role assignment
* with an associated token that permits read-only access to the metadata and
* all files (regardless of if the files are restricted or not) of a draft
* version of a dataset.
*
- * As of this note, a second option - to create a Private URL that provides an
+ * As of this note, a second option - to create a Preview URL that provides an
* anonymized view of the dataset has been added. This option works the same as
* the original except that it hides author names in the citation block, hides
* the values for an admin specified list of metadata fields, disables citation
* downloads, and disables API access (except for file and file thumbnail
* downloads which are used by the UI).
*
- * The primary use case for a Private URL is for journal editors to send a link
+ * The primary use case for a Preview URL is for journal editors to send a link
* to reviewers of a dataset before publication. In most cases, these journal
* editors do not permit depositors to publish on their own, which is to say
* they only allow depositors to have the "Contributor" role on the datasets
@@ -24,42 +24,42 @@
* the depositor, who is in charge of both the security of the dataset and the
* timing of when the dataset is published.
*
- * A secondary use case for a Private URL is for depositors who have the ability
+ * A secondary use case for a Preview URL is for depositors who have the ability
* to manage permissions on their dataset (depositors who have the "Curator" or
* "Admin" role, which grants much more power than the "Contributor" role) to
* send a link to coauthors or other trusted parties to preview the dataset
* before the depositors publish the dataset on their own. For better security,
* these depositors could ask their coauthors to create Dataverse accounts and
- * assign roles to them directly, rather than using a Private URL which requires
+ * assign roles to them directly, rather than using a Preview URL which requires
* no username or password.
*
* As of this note, a second option aimed specifically at the review use case -
- * to create a Private URL that provides an anonymized view of the dataset - has
+ * to create a Preview URL that provides an anonymized view of the dataset - has
* been added. This option works the same as the original except that it hides
* author names in the citation block, hides the values for an admin specified
* list of metadata fields, disables citation downloads, and disables API access
* (except for file and file thumbnail downloads which are used by the UI).
*
- * The token associated with the Private URL role assignment that can be used
+ * The token associated with the Preview URL role assignment that can be used
* either in the GUI or, for the non-anonymized-access option, via the API to
* elevate privileges beyond what a "Guest" can see. The ability to use a
- * Private URL token via API was added mostly to facilitate automated testing of
- * the feature but the far more common case is expected to be use of the Private
+ * Preview URL token via API was added mostly to facilitate automated testing of
+ * the feature but the far more common case is expected to be use of the Preview
* URL token in a link that is clicked to open a browser, similar to links
* shared via Dropbox, Google, etc.
*
- * When reviewers click a Private URL their browser sessions are set to the
+ * When reviewers click a Preview URL their browser sessions are set to the
* "{@link edu.harvard.iq.dataverse.authorization.users.PrivateUrlUser}" that
* has the "Member" role only on the dataset in question and redirected to that
* dataset, where they will see an indication in blue at the top of the page
* that they are viewing an unpublished dataset. If the reviewer happens to be
* logged into Dataverse already, clicking the link will log them out because
* the review is meant to be blind. Because the dataset is always in draft when
- * a Private URL is in effect, no downloads or any other activity by the
- * reviewer are logged to the guestbook. All reviewers click the same Private
+ * a Preview URL is in effect, no downloads or any other activity by the
+ * reviewer are logged to the guestbook. All reviewers click the same Preview
* URL containing the same token, and with the exception of an IP address being
* logged, it should be impossible to trace which reviewers have clicked a
- * Private URL. If the reviewer navigates to the home page, the session is set
+ * Preview URL. If the reviewer navigates to the home page, the session is set
* to the Guest user and they will see what a Guest would see.
*
* The "Member" role is used because it contains the necessary read-only
@@ -76,51 +76,51 @@
* version. A Member can also download restricted files that have been deleted
* from previously published versions.
*
- * Likewise, when a Private URL token is used via API, commands are executed
+ * Likewise, when a Preview URL token is used via API, commands are executed
* using the "PrivateUrlUser" that has the "Member" role only on the dataset in
* question. This means that read-only operations such as downloads of the
- * dataset's files are permitted. The Search API does not respect the Private
+ * dataset's files are permitted. The Search API does not respect the Preview
* URL token but you can download files using the Access API, and, with the
* non-anonymized-access option, download unpublished metadata using the Native
* API.
*
- * A Private URL cannot be created for a published version of a dataset. In the
+ * A Preview URL cannot be created for a published version of a dataset. In the
* GUI, you will be reminded of this fact with a popup. The API will explain
* this as well.
*
- * An anonymized-access Private URL can't be created if any published dataset
+ * An anonymized-access Preview URL can't be created if any published dataset
* version exists. The primary reason for this is that, since datasets have
* DOIs, the full metadata about published versions is available directly from
* the DOI provider. (While the metadata for that version could be somewhat
* different, in practice it would probably provide a means of identifying
* some/all of the authors).
*
- * If a draft dataset containing a Private URL is
- * published, the Private URL is deleted. This means that reviewers who click
+ * If a draft dataset containing a Preview URL is
+ * published, the Preview URL is deleted. This means that reviewers who click
* the link after publication will see a 404.
*
- * If a post-publication draft containing a Private URL is deleted, the Private
+ * If a post-publication draft containing a Preview URL is deleted, the Preview
* URL is deleted. This is to ensure that if a new draft is created in the
* future, a new token will be used.
*
- * The creation and deletion of a Private URL are limited to the "Curator" and
+ * The creation and deletion of a Preview URL are limited to the "Curator" and
* "Admin" roles because only those roles have the permission called
* "ManageDatasetPermissions", which is the permission used by the
* "AssignRoleCommand" and "RevokeRoleCommand" commands. If you have the
- * permission to create or delete a Private URL, the fact that a Private URL is
+ * permission to create or delete a Preview URL, the fact that a Preview URL is
* enabled for a dataset will be indicated in blue at the top of the page.
* Success messages are shown at the top of the page when you create or delete a
- * Private URL. In the GUI, deleting a Private URL is called "disabling" and you
+ * Preview URL. In the GUI, deleting a Preview URL is called "disabling" and you
* will be prompted for a confirmation. No matter what you call it the role is
- * revoked. You can also delete a Private URL by revoking the role.
+ * revoked. You can also delete a Preview URL by revoking the role.
*
* A "Contributor" does not have the "ManageDatasetPermissions" permission and
- * cannot see "Permissions" nor "Private URL" under the "Edit" menu of their
- * dataset. When a Curator or Admin has enabled a Private URL on a Contributor's
- * dataset, the Contributor does not see a visual indication that a Private URL
+ * cannot see "Permissions" nor "Preview URL" under the "Edit" menu of their
+ * dataset. When a Curator or Admin has enabled a Preview URL on a Contributor's
+ * dataset, the Contributor does not see a visual indication that a Preview URL
* has been enabled for their dataset.
*
- * There is no way for an "Admin" or "Curator" to see when a Private URL was
+ * There is no way for an "Admin" or "Curator" to see when a Preview URL was
* created or deleted for a dataset but someone who has access to the database
* can see that the following commands are logged to the "actionlogrecord"
* database table:
@@ -129,7 +129,7 @@
*
*
- * See also the Private URL To Unpublished Dataset BRD at
* https://docs.google.com/document/d/1FT47QkZKcmjSgRnePaJO2g1nzcotLyN3Yb2ORvBr6cs/edit?usp=sharing
*/
diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties
index 5f3e4c33e0b..3ee017e06de 100644
--- a/src/main/java/propertyFiles/Bundle.properties
+++ b/src/main/java/propertyFiles/Bundle.properties
@@ -1457,7 +1457,7 @@ dataset.editBtn.itemLabel.metadata=Metadata
dataset.editBtn.itemLabel.terms=Terms
dataset.editBtn.itemLabel.permissions=Permissions
dataset.editBtn.itemLabel.thumbnailsAndWidgets=Thumbnails + Widgets
-dataset.editBtn.itemLabel.privateUrl=Private URL
+dataset.editBtn.itemLabel.privateUrl=Preview URL
dataset.editBtn.itemLabel.permissionsDataset=Dataset
dataset.editBtn.itemLabel.permissionsFile=Restricted Files
dataset.editBtn.itemLabel.deleteDataset=Delete Dataset
@@ -1722,22 +1722,22 @@ dataset.requestAccessToRestrictedFiles=You may request access to any restricted
dataset.requestAccessToRestrictedFilesWithEmbargo=Embargoed files cannot be accessed during the embargo period. If your selection contains restricted files, you may request access to them by clicking the Request Access button.
dataset.privateurl.infoMessageAuthor=Privately share this dataset before it is published: {0}
dataset.privateurl.infoMessageReviewer=This unpublished dataset is being privately shared.
-dataset.privateurl.header=Unpublished Dataset Private URL
-dataset.privateurl.tip=Use a Private URL to allow those without Dataverse accounts to access your unpublished dataset. For more information about the Private URL feature, please refer to the User Guide.
-dataset.privateurl.absent=Private URL has not been created.
-dataset.privateurl.createPrivateUrl=Create Private URL
+dataset.privateurl.header=Unpublished Dataset Preview URL
+dataset.privateurl.tip=Use a Preview URL to allow those without Dataverse accounts to access your unpublished dataset. For more information about the Preview URL feature, please refer to the User Guide.
+dataset.privateurl.absent=Preview URL has not been created.
+dataset.privateurl.createPrivateUrl=Create Preview URL
dataset.privateurl.createPrivateUrl.anonymized=Create URL for Anonymized Access
dataset.privateurl.createPrivateUrl.anonymized.unavailable=Anonymized Access is not available once a version of the dataset has been published
-dataset.privateurl.disablePrivateUrl=Disable Private URL
-dataset.privateurl.disablePrivateUrlConfirm=Yes, Disable Private URL
-dataset.privateurl.disableConfirmationText=Are you sure you want to disable the Private URL? If you have shared the Private URL with others they will no longer be able to use it to access your unpublished dataset.
-dataset.privateurl.cannotCreate=Private URL can only be used with unpublished versions of datasets.
-dataset.privateurl.roleassigeeTitle=Private URL Enabled
+dataset.privateurl.disablePrivateUrl=Disable Preview URL
+dataset.privateurl.disablePrivateUrlConfirm=Yes, Disable Preview URL
+dataset.privateurl.disableConfirmationText=Are you sure you want to disable the Preview URL? If you have shared the Preview URL with others they will no longer be able to use it to access your unpublished dataset.
+dataset.privateurl.cannotCreate=Preview URL can only be used with unpublished versions of datasets.
+dataset.privateurl.roleassigeeTitle=Preview URL Enabled
dataset.privateurl.createdSuccess=Success!
-dataset.privateurl.full=This Private URL provides full read access to the dataset
-dataset.privateurl.anonymized=This Private URL provides access to the anonymized dataset
-dataset.privateurl.disabledSuccess=You have successfully disabled the Private URL for this unpublished dataset.
-dataset.privateurl.noPermToCreate=To create a Private URL you must have the following permissions: {0}.
+dataset.privateurl.full=This Preview URL provides full read access to the dataset
+dataset.privateurl.anonymized=This Preview URL provides access to the anonymized dataset
+dataset.privateurl.disabledSuccess=You have successfully disabled the Preview URL for this unpublished dataset.
+dataset.privateurl.noPermToCreate=To create a Preview URL you must have the following permissions: {0}.
dataset.externalstatus.header=Curation Status Changed
dataset.externalstatus.removed=Curation Status Removed
dataset.externalstatus.info=Curation Status is now "{0}"
@@ -2719,8 +2719,8 @@ datasets.api.grant.role.assignee.has.role.error=User already has this role for t
datasets.api.revoke.role.not.found.error="Role assignment {0} not found"
datasets.api.revoke.role.success=Role {0} revoked for assignee {1} in {2}
datasets.api.privateurl.error.datasetnotfound=Could not find dataset.
-datasets.api.privateurl.error.alreadyexists=Private URL already exists for this dataset.
-datasets.api.privateurl.error.notdraft=Can't create Private URL because the latest version of this dataset is not a draft.
+datasets.api.privateurl.error.alreadyexists=Preview URL already exists for this dataset.
+datasets.api.privateurl.error.notdraft=Can't create Preview URL because the latest version of this dataset is not a draft.
datasets.api.privateurl.anonymized.error.released=Can't create a URL for anonymized access because this dataset has been published.
datasets.api.creationdate=Date Created
datasets.api.modificationdate=Last Modified Date
diff --git a/src/main/webapp/previewurl-popup-fragment.xhtml b/src/main/webapp/previewurl-popup-fragment.xhtml
new file mode 100644
index 00000000000..f3963ad899e
--- /dev/null
+++ b/src/main/webapp/previewurl-popup-fragment.xhtml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ TODO supply a title
+
+
+
+
TODO write content
+
+
From 06d4fa50ddcdd11679654032e898e8e450f669fc Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Tue, 8 Oct 2024 14:27:00 -0400
Subject: [PATCH 036/270] #8184 fix integration test text
---
src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
index 8e38a96fc97..24168ff4d76 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
@@ -1676,7 +1676,7 @@ public void testPrivateUrl() {
List assignments = with(roleAssignments.body().asString()).param("member", "member").getJsonObject("data.findAll { data -> data._roleAlias == member }");
assertEquals(1, assignments.size());
PrivateUrlUser privateUrlUser = new PrivateUrlUser(datasetId);
- assertEquals("Private URL Enabled", privateUrlUser.getDisplayInfo().getTitle());
+ assertEquals("Preview URL Enabled", privateUrlUser.getDisplayInfo().getTitle());
List assigneeShouldExistForPrivateUrlUser = with(roleAssignments.body().asString()).param("assigneeString", privateUrlUser.getIdentifier()).getJsonObject("data.findAll { data -> data.assignee == assigneeString }");
logger.info(assigneeShouldExistForPrivateUrlUser + " found for " + privateUrlUser.getIdentifier());
assertEquals(1, assigneeShouldExistForPrivateUrlUser.size());
From bc8e88b26d20e66969f8c37f5a506f16f02f08b7 Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Tue, 8 Oct 2024 16:58:23 -0400
Subject: [PATCH 037/270] Delete previewurl-popup-fragment.xhtml
---
src/main/webapp/previewurl-popup-fragment.xhtml | 15 ---------------
1 file changed, 15 deletions(-)
delete mode 100644 src/main/webapp/previewurl-popup-fragment.xhtml
diff --git a/src/main/webapp/previewurl-popup-fragment.xhtml b/src/main/webapp/previewurl-popup-fragment.xhtml
deleted file mode 100644
index f3963ad899e..00000000000
--- a/src/main/webapp/previewurl-popup-fragment.xhtml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
- TODO supply a title
-
-
-
-
TODO write content
-
-
From 8e956625205c8b0266fc002e1edf11decda2a3f0 Mon Sep 17 00:00:00 2001
From: Don Sizemore
Date: Thu, 10 Oct 2024 11:01:12 -0400
Subject: [PATCH 038/270] #10889 bump container POSTGRES_VERSION to 17
---
.env | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.env b/.env
index d5cffcec0aa..9d604630073 100644
--- a/.env
+++ b/.env
@@ -1,5 +1,5 @@
APP_IMAGE=gdcc/dataverse:unstable
-POSTGRES_VERSION=16
+POSTGRES_VERSION=17
DATAVERSE_DB_USER=dataverse
SOLR_VERSION=9.3.0
-SKIP_DEPLOY=0
\ No newline at end of file
+SKIP_DEPLOY=0
From 0f8f267b0fb50d1a03fb71cfb00ce2639ef82644 Mon Sep 17 00:00:00 2001
From: Don Sizemore
Date: Thu, 10 Oct 2024 11:20:33 -0400
Subject: [PATCH 039/270] #10889 add Postgres/FlyWay release notes
---
doc/release-notes/10889_bump_PG17_FlyWay10.md | 3 +++
1 file changed, 3 insertions(+)
create mode 100644 doc/release-notes/10889_bump_PG17_FlyWay10.md
diff --git a/doc/release-notes/10889_bump_PG17_FlyWay10.md b/doc/release-notes/10889_bump_PG17_FlyWay10.md
new file mode 100644
index 00000000000..012627bd43c
--- /dev/null
+++ b/doc/release-notes/10889_bump_PG17_FlyWay10.md
@@ -0,0 +1,3 @@
+This release bumps both the Postgres JDBC driver and Flyway versions. This should better support Postgres version 17, and as of version 10 Flyway no longer requires a paid subscription to support older versions of Postgres.
+
+While we don't encourage the use of older Postgres versions, this flexibility may benefit some of our long-standing installations in their upgrade paths.
From 9e53e0f7046bba58466fdee466a3ffa463836874 Mon Sep 17 00:00:00 2001
From: Don Sizemore
Date: Fri, 11 Oct 2024 14:40:58 -0400
Subject: [PATCH 040/270] #10889 update Docker PG version, state version used
in automated testing
---
doc/release-notes/10889_bump_PG17_FlyWay10.md | 2 +-
docker/compose/demo/compose.yml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/doc/release-notes/10889_bump_PG17_FlyWay10.md b/doc/release-notes/10889_bump_PG17_FlyWay10.md
index 012627bd43c..0f74568e5cd 100644
--- a/doc/release-notes/10889_bump_PG17_FlyWay10.md
+++ b/doc/release-notes/10889_bump_PG17_FlyWay10.md
@@ -1,3 +1,3 @@
This release bumps both the Postgres JDBC driver and Flyway versions. This should better support Postgres version 17, and as of version 10 Flyway no longer requires a paid subscription to support older versions of Postgres.
-While we don't encourage the use of older Postgres versions, this flexibility may benefit some of our long-standing installations in their upgrade paths.
+While we don't encourage the use of older Postgres versions, this flexibility may benefit some of our long-standing installations in their upgrade paths. Postgres 13 remains the version used with automated testing.
diff --git a/docker/compose/demo/compose.yml b/docker/compose/demo/compose.yml
index 33e7b52004b..62444706950 100644
--- a/docker/compose/demo/compose.yml
+++ b/docker/compose/demo/compose.yml
@@ -76,7 +76,7 @@ services:
postgres:
container_name: "postgres"
hostname: postgres
- image: postgres:13
+ image: postgres:17
restart: on-failure
environment:
- POSTGRES_USER=dataverse
From 73dd0dd34064cca50304586588eef749053b9637 Mon Sep 17 00:00:00 2001
From: Jim Myers
Date: Tue, 15 Oct 2024 17:11:43 -0400
Subject: [PATCH 041/270] fix relationType display value bug
---
src/main/java/edu/harvard/iq/dataverse/DatasetVersion.java | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetVersion.java b/src/main/java/edu/harvard/iq/dataverse/DatasetVersion.java
index 0433c425fd2..ac5923b95bf 100644
--- a/src/main/java/edu/harvard/iq/dataverse/DatasetVersion.java
+++ b/src/main/java/edu/harvard/iq/dataverse/DatasetVersion.java
@@ -1390,7 +1390,10 @@ public List getRelatedPublications() {
relatedPublication.setIdNumber(subField.getDisplayValue());
break;
case DatasetFieldConstant.publicationRelationType:
- relatedPublication.setRelationType(subField.getDisplayValue());
+ List values = subField.getValues_nondisplay();
+ if (!values.isEmpty()) {
+ relatedPublication.setRelationType(values.get(0)); //only one value allowed
+ }
break;
}
}
From e85c4228524620d3e5db94b266a8967e10e0be0f Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Wed, 16 Oct 2024 09:44:29 -0400
Subject: [PATCH 042/270] #8184 update popup and Bundle
---
src/main/java/propertyFiles/Bundle.properties | 11 +++-
src/main/webapp/dataset.xhtml | 64 ++++++++++++++++---
2 files changed, 64 insertions(+), 11 deletions(-)
diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties
index 3ee017e06de..ce4d53a5eb9 100644
--- a/src/main/java/propertyFiles/Bundle.properties
+++ b/src/main/java/propertyFiles/Bundle.properties
@@ -1723,9 +1723,18 @@ dataset.requestAccessToRestrictedFilesWithEmbargo=Embargoed files cannot be acce
dataset.privateurl.infoMessageAuthor=Privately share this dataset before it is published: {0}
dataset.privateurl.infoMessageReviewer=This unpublished dataset is being privately shared.
dataset.privateurl.header=Unpublished Dataset Preview URL
-dataset.privateurl.tip=Use a Preview URL to allow those without Dataverse accounts to access your unpublished dataset. For more information about the Preview URL feature, please refer to the User Guide.
+dataset.privateurl.tip=To cite this data in publications, use the dataset's persistent ID instead of this URL. For more information about the Preview URL feature, please refer to the User Guide.
+dataset.privateurl.onlyone=Only one Preview URL can be active for a single dataset.
dataset.privateurl.absent=Preview URL has not been created.
+dataset.privateurl.general.button.label=Create General Preview URL
+dataset.privateurl.general.description=Create a URL that others can use to review this dataset version before it is published. they will be able to access all files in the dataset and see all metadata, including metadata that may identify the dataset's authors.
+dataset.privateurl.general.title=General Preview
+dataset.privateurl.anonymous.title=Anonymous Preview
+dataset.privateurl.anonymous.button.label=Create Anonymous Preview URL
+dataset.privateurl.anonymous.description=Create a URL that others can use to access an anonymized view of this unpublished dataset version. Metadata that could identify the dataset author will not be displayed. Non-identifying metadata will be visible.
+dataset.privateurl.anonymous.description.paragraph.two=The dataset's files are not changed and will be accessible if they're not restricted. Users of the preview URL will be able to see the name of the repository and the name of the collection that this dataset is in, which may expose the dataset author's identities.
dataset.privateurl.createPrivateUrl=Create Preview URL
+dataset.privateurl.introduction=You can create a Preview URL to copy and share with others who will not need a repository account to review this unpublished dataset version. Once the dataset is published ot if the URL is disabled, the URL will no longer work and will point to a "Page not found" page.
dataset.privateurl.createPrivateUrl.anonymized=Create URL for Anonymized Access
dataset.privateurl.createPrivateUrl.anonymized.unavailable=Anonymized Access is not available once a version of the dataset has been published
dataset.privateurl.disablePrivateUrl=Disable Preview URL
diff --git a/src/main/webapp/dataset.xhtml b/src/main/webapp/dataset.xhtml
index 6de0f00e94e..9629b3bbc85 100644
--- a/src/main/webapp/dataset.xhtml
+++ b/src/main/webapp/dataset.xhtml
@@ -1178,11 +1178,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
From bc798344ccabb9bfeacd047e7444ed83670e9cec Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Wed, 16 Oct 2024 10:48:19 -0400
Subject: [PATCH 044/270] #8184 revert redirect url
---
.../java/edu/harvard/iq/dataverse/privateurl/PrivateUrl.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrl.java b/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrl.java
index 63b5bf03ea7..beb676f60d1 100644
--- a/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrl.java
+++ b/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrl.java
@@ -30,7 +30,7 @@ public class PrivateUrl {
public PrivateUrl(RoleAssignment roleAssignment, Dataset dataset, String dataverseSiteUrl) {
this.token = roleAssignment.getPrivateUrlToken();
- this.link = dataverseSiteUrl + "/previewurl.xhtml?token=" + token;
+ this.link = dataverseSiteUrl + "/privateurl.xhtml?token=" + token;
this.dataset = dataset;
this.roleAssignment = roleAssignment;
}
From 53e2f0e87bc76fc2767ab72a15d0877241228787 Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Wed, 16 Oct 2024 11:17:04 -0400
Subject: [PATCH 045/270] #8184 fix unit tests
---
.../iq/dataverse/authorization/users/PrivateUrlUserTest.java | 2 +-
.../engine/command/impl/CreatePrivateUrlCommandTest.java | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/test/java/edu/harvard/iq/dataverse/authorization/users/PrivateUrlUserTest.java b/src/test/java/edu/harvard/iq/dataverse/authorization/users/PrivateUrlUserTest.java
index a8dda2f6a7e..d3c5cdca470 100644
--- a/src/test/java/edu/harvard/iq/dataverse/authorization/users/PrivateUrlUserTest.java
+++ b/src/test/java/edu/harvard/iq/dataverse/authorization/users/PrivateUrlUserTest.java
@@ -38,7 +38,7 @@ void getIdentifier() {
@Test
void testGetDisplayInfo() {
RoleAssigneeDisplayInfo displayInfo = privateUrlUser.getDisplayInfo();
- assertEquals("Private URL Enabled", displayInfo.getTitle());
+ assertEquals("Preview URL Enabled", displayInfo.getTitle());
assertNull(displayInfo.getEmailAddress());
}
}
diff --git a/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/CreatePrivateUrlCommandTest.java b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/CreatePrivateUrlCommandTest.java
index 508eac46cb4..73cc867cf24 100644
--- a/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/CreatePrivateUrlCommandTest.java
+++ b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/CreatePrivateUrlCommandTest.java
@@ -171,7 +171,7 @@ public void testCreatePrivateUrlSuccessfully() throws CommandException {
assertEquals(expectedUser.getIdentifier(), privateUrl.getRoleAssignment().getAssigneeIdentifier());
assertEquals(expectedUser.isSuperuser(), false);
assertEquals(expectedUser.isAuthenticated(), false);
- assertEquals(expectedUser.getDisplayInfo().getTitle(), "Private URL Enabled");
+ assertEquals(expectedUser.getDisplayInfo().getTitle(), "Preview URL Enabled");
assertNotNull(privateUrl.getToken());
assertEquals("https://dataverse.example.edu/privateurl.xhtml?token=" + privateUrl.getToken(), privateUrl.getLink());
}
@@ -188,7 +188,7 @@ public void testCreateAnonymizedAccessPrivateUrlSuccessfully() throws CommandExc
assertEquals(expectedUser.getIdentifier(), privateUrl.getRoleAssignment().getAssigneeIdentifier());
assertEquals(expectedUser.isSuperuser(), false);
assertEquals(expectedUser.isAuthenticated(), false);
- assertEquals(expectedUser.getDisplayInfo().getTitle(), "Private URL Enabled");
+ assertEquals(expectedUser.getDisplayInfo().getTitle(), "Preview URL Enabled");
assertNotNull(privateUrl.getToken());
assertTrue(privateUrl.isAnonymizedAccess());
assertEquals("https://dataverse.example.edu/privateurl.xhtml?token=" + privateUrl.getToken(), privateUrl.getLink());
From 57960c612bffa782fd4f75d9ed01e3f3307391e3 Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Wed, 16 Oct 2024 14:44:39 -0400
Subject: [PATCH 046/270] #8184 fix existing test
---
.../command/impl/CreatePrivateUrlCommandTest.java | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/CreatePrivateUrlCommandTest.java b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/CreatePrivateUrlCommandTest.java
index 73cc867cf24..e0060af924b 100644
--- a/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/CreatePrivateUrlCommandTest.java
+++ b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/CreatePrivateUrlCommandTest.java
@@ -18,7 +18,9 @@
import edu.harvard.iq.dataverse.search.IndexServiceBean;
import edu.harvard.iq.dataverse.search.SolrIndexServiceBean;
import edu.harvard.iq.dataverse.util.SystemConfig;
+import java.sql.Timestamp;
import java.util.ArrayList;
+import java.util.Date;
import java.util.List;
import java.util.concurrent.Future;
@@ -195,15 +197,17 @@ public void testCreateAnonymizedAccessPrivateUrlSuccessfully() throws CommandExc
}
@Test
- public void testAttemptCreateAnonymizedAccessPrivateUrlOnReleased() {
+ public void testAttemptCreateAnonymizedAccessPrivateUrlOnReleased() throws CommandException {
dataset = new Dataset();
List versions = new ArrayList<>();
+ dataset.setPublicationDate(new Timestamp(new Date().getTime()));
DatasetVersion datasetVersion = new DatasetVersion();
datasetVersion.setVersionState(DatasetVersion.VersionState.RELEASED);
DatasetVersion datasetVersion2 = new DatasetVersion();
-
- versions.add(datasetVersion);
+ datasetVersion2.setVersionState(DatasetVersion.VersionState.DRAFT);
+
versions.add(datasetVersion2);
+ versions.add(datasetVersion);
dataset.setVersions(versions);
dataset.setId(versionIsReleased);
PrivateUrl privateUrl = null;
@@ -211,6 +215,7 @@ public void testAttemptCreateAnonymizedAccessPrivateUrlOnReleased() {
privateUrl = testEngine.submit(new CreatePrivateUrlCommand(null, dataset, true));
assertTrue(false);
} catch (CommandException ex) {
+
}
assertNull(privateUrl);
}
From 1b8a257adfe93bad1476bc325131d9206f488f66 Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Wed, 16 Oct 2024 15:20:01 -0400
Subject: [PATCH 047/270] #8184 update constructor/test urls
---
.../java/edu/harvard/iq/dataverse/privateurl/PrivateUrl.java | 2 +-
.../engine/command/impl/CreatePrivateUrlCommandTest.java | 4 ++--
.../harvard/iq/dataverse/privateurl/PrivateUrlUtilTest.java | 2 +-
.../edu/harvard/iq/dataverse/util/json/JsonPrinterTest.java | 2 +-
4 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrl.java b/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrl.java
index beb676f60d1..63b5bf03ea7 100644
--- a/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrl.java
+++ b/src/main/java/edu/harvard/iq/dataverse/privateurl/PrivateUrl.java
@@ -30,7 +30,7 @@ public class PrivateUrl {
public PrivateUrl(RoleAssignment roleAssignment, Dataset dataset, String dataverseSiteUrl) {
this.token = roleAssignment.getPrivateUrlToken();
- this.link = dataverseSiteUrl + "/privateurl.xhtml?token=" + token;
+ this.link = dataverseSiteUrl + "/previewurl.xhtml?token=" + token;
this.dataset = dataset;
this.roleAssignment = roleAssignment;
}
diff --git a/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/CreatePrivateUrlCommandTest.java b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/CreatePrivateUrlCommandTest.java
index e0060af924b..0ba29f74774 100644
--- a/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/CreatePrivateUrlCommandTest.java
+++ b/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/CreatePrivateUrlCommandTest.java
@@ -175,7 +175,7 @@ public void testCreatePrivateUrlSuccessfully() throws CommandException {
assertEquals(expectedUser.isAuthenticated(), false);
assertEquals(expectedUser.getDisplayInfo().getTitle(), "Preview URL Enabled");
assertNotNull(privateUrl.getToken());
- assertEquals("https://dataverse.example.edu/privateurl.xhtml?token=" + privateUrl.getToken(), privateUrl.getLink());
+ assertEquals("https://dataverse.example.edu/previewurl.xhtml?token=" + privateUrl.getToken(), privateUrl.getLink());
}
@Test
@@ -193,7 +193,7 @@ public void testCreateAnonymizedAccessPrivateUrlSuccessfully() throws CommandExc
assertEquals(expectedUser.getDisplayInfo().getTitle(), "Preview URL Enabled");
assertNotNull(privateUrl.getToken());
assertTrue(privateUrl.isAnonymizedAccess());
- assertEquals("https://dataverse.example.edu/privateurl.xhtml?token=" + privateUrl.getToken(), privateUrl.getLink());
+ assertEquals("https://dataverse.example.edu/previewurl.xhtml?token=" + privateUrl.getToken(), privateUrl.getLink());
}
@Test
diff --git a/src/test/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlUtilTest.java b/src/test/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlUtilTest.java
index da94b288bee..f06be37578d 100644
--- a/src/test/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlUtilTest.java
+++ b/src/test/java/edu/harvard/iq/dataverse/privateurl/PrivateUrlUtilTest.java
@@ -277,7 +277,7 @@ public void testGetPrivateUrlFromRoleAssignmentSuccess() {
PrivateUrl privateUrl = PrivateUrlUtil.getPrivateUrlFromRoleAssignment(ra, dataverseSiteUrl);
assertNotNull(privateUrl);
assertEquals(new Long(42), privateUrl.getDataset().getId());
- assertEquals("https://dataverse.example.edu/privateurl.xhtml?token=cd71e9d7-73a7-4ec8-b890-3d00499e8693", privateUrl.getLink());
+ assertEquals("https://dataverse.example.edu/previewurl.xhtml?token=cd71e9d7-73a7-4ec8-b890-3d00499e8693", privateUrl.getLink());
}
@Test
diff --git a/src/test/java/edu/harvard/iq/dataverse/util/json/JsonPrinterTest.java b/src/test/java/edu/harvard/iq/dataverse/util/json/JsonPrinterTest.java
index 11da71e1980..30cef574a6a 100644
--- a/src/test/java/edu/harvard/iq/dataverse/util/json/JsonPrinterTest.java
+++ b/src/test/java/edu/harvard/iq/dataverse/util/json/JsonPrinterTest.java
@@ -133,7 +133,7 @@ public void testJson_PrivateUrl() {
assertNotNull(job);
JsonObject jsonObject = job.build();
assertEquals("e1d53cf6-794a-457a-9709-7c07629a8267", jsonObject.getString("token"));
- assertEquals("https://dataverse.example.edu/privateurl.xhtml?token=e1d53cf6-794a-457a-9709-7c07629a8267", jsonObject.getString("link"));
+ assertEquals("https://dataverse.example.edu/previewurl.xhtml?token=e1d53cf6-794a-457a-9709-7c07629a8267", jsonObject.getString("link"));
assertEquals("e1d53cf6-794a-457a-9709-7c07629a8267", jsonObject.getJsonObject("roleAssignment").getString("privateUrlToken"));
assertEquals(PrivateUrlUser.PREFIX + "42", jsonObject.getJsonObject("roleAssignment").getString("assignee"));
}
From 337ea789b82740174d1c4930f483261b3188f958 Mon Sep 17 00:00:00 2001
From: Philip Durbin
Date: Wed, 16 Oct 2024 16:15:04 -0400
Subject: [PATCH 048/270] update making releases page post 6.4 #10931
---
.../source/developers/making-releases.rst | 92 ++++++++++++++-----
1 file changed, 71 insertions(+), 21 deletions(-)
diff --git a/doc/sphinx-guides/source/developers/making-releases.rst b/doc/sphinx-guides/source/developers/making-releases.rst
index 4936e942389..2afdfd2eb2f 100755
--- a/doc/sphinx-guides/source/developers/making-releases.rst
+++ b/doc/sphinx-guides/source/developers/making-releases.rst
@@ -10,10 +10,43 @@ Introduction
This document is about releasing the main Dataverse app (https://github.com/IQSS/dataverse). See :doc:`making-library-releases` for how to release our various libraries. Other projects have their own release documentation.
-The steps below describe making both regular releases and hotfix releases.
-
Below you'll see branches like "develop" and "master" mentioned. For more on our branching strategy, see :doc:`version-control`.
+Regular or Hotfix?
+------------------
+
+Early on, make sure it's clear what type of release this is. The steps below describe making both regular releases and hotfix releases.
+
+- regular
+
+ - e.g. 6.5 (minor)
+ - e.g. 7.0 (major)
+
+- hotfix
+
+ - e.g. 6.4.1 (patch)
+ - e.g. 7.0.1 (patch)
+
+Ensure Issues Have Been Created
+-------------------------------
+
+In advance of a release, GitHub issues should have been created already that capture certain steps. See https://github.com/IQSS/dataverse-pm/issues/335 for examples.
+
+Declare a Code Freeze
+---------------------
+
+The following steps are made more difficult if code is changing in the "develop" branch. Declare a code freeze until the release is out. Do not allow pull requests to be merged.
+
+Conduct Performance Testing
+---------------------------
+
+See :doc:`/qa/performance-tests` for details.
+
+Conduct Smoke Testing
+---------------------
+
+See :doc:`/qa/testing-approach` for details.
+
.. _write-release-notes:
Write Release Notes
@@ -23,26 +56,46 @@ Developers express the need for an addition to release notes by creating a "rele
The task at or near release time is to collect these snippets into a single file.
-- Create an issue in GitHub to track the work of creating release notes for the upcoming release.
+- Find the issue in GitHub that tracks the work of creating release notes for the upcoming release.
- Create a branch, add a .md file for the release (ex. 5.10.1 Release Notes) in ``/doc/release-notes`` and write the release notes, making sure to pull content from the release note snippets mentioned above. Snippets may not include any issue number or pull request number in the text so be sure copy the number from the filename of the snippet into the final release note.
- Delete (``git rm``) the release note snippets as the content is added to the main release notes file.
- Include instructions describing the steps required to upgrade the application from the previous version. These must be customized for release numbers and special circumstances such as changes to metadata blocks and infrastructure.
-- Take the release notes .md through the regular Code Review and QA process. That is, make a pull request.
+- Take the release notes .md through the regular Code Review and QA process. That is, make a pull request. Here's an example: https://github.com/IQSS/dataverse/pull/10866
-Create a GitHub Issue and Branch for the Release
-------------------------------------------------
+Deploy Release Candidate to Demo
+--------------------------------
+
+First, build the release candidate.
+
+ssh into the dataverse-internal server and undeploy the current war file.
+
+Go to https://jenkins.dataverse.org/job/IQSS_Dataverse_Internal/ and make the following adjustments to the config:
+
+- Repository URL: ``https://github.com/IQSS/dataverse.git``
+- Branch Specifier (blank for 'any'): ``*/develop``
+- Execute shell: Update version in filenames to ``dataverse-5.10.war`` (for example)
+
+Click "Save" then "Build Now".
+
+This will build the war file, and then automatically deploy it on dataverse-internal. Verify that the application has deployed successfully.
+
+You can scp the war file to the demo server or download it from https://jenkins.dataverse.org/job/IQSS_Dataverse_Internal/ws/target/
+
+ssh into the demo server and follow the upgrade instructions in the release notes.
+
+Prepare Release Branch
+----------------------
+
+The release branch will have the final changes such as bumping the version number.
Usually we branch from the "develop" branch to create the release branch. If we are creating a hotfix for a particular version (5.11, for example), we branch from the tag (e.g. ``v5.11``).
-Use the GitHub issue number and the release tag for the name of the branch. (e.g. ``8583-update-version-to-v5.10.1``
+Create a release branch named after the issue that tracks bumping the version with a descriptive name like "10852-bump-to-6.4" from https://github.com/IQSS/dataverse/pull/10871.
**Note:** the changes below must be the very last commits merged into the develop branch before it is merged into master and tagged for the release!
Make the following changes in the release branch.
-Bump Version Numbers and Prepare Container Tags
------------------------------------------------
-
Increment the version number to the milestone (e.g. 5.10.1) in the following two files:
- modules/dataverse-parent/pom.xml -> ```` -> ```` (e.g. `pom.xml commit `_)
@@ -58,14 +111,11 @@ Return to the parent pom and make the following change, which is necessary for p
(Before you make this change the value should be ``${parsedVersion.majorVersion}.${parsedVersion.nextMinorVersion}``. Later on, after cutting a release, we'll change it back to that value.)
-Check in the Changes Above into a Release Branch and Merge It
--------------------------------------------------------------
-
For a regular release, make the changes above in the release branch you created, make a pull request, and merge it into the "develop" branch. Like usual, you can safely delete the branch after the merge is complete.
If you are making a hotfix release, make the pull request against the "master" branch. Do not delete the branch after merging because we will later merge it into the "develop" branch to pick up the hotfix. More on this later.
-Either way, as usual, you should ensure that all tests are passing. Please note that you will need to bump the version in `jenkins.yml `_ in dataverse-ansible to get the tests to pass. Consider doing this before making the pull request. Alternatively, you can bump jenkins.yml after making the pull request and re-run the Jenkins job to make sure tests pass.
+Either way, as usual, you should ensure that all tests are passing. Please note that you will need to bump the version in `jenkins.yml `_ in dataverse-ansible to get the tests to pass. Consider doing this before making the pull request. Alternatively, you can bump jenkins.yml after making the pull request and re-run the Jenkins job to make sure tests pass.
Merge "develop" into "master"
-----------------------------
@@ -94,7 +144,7 @@ After the "master" branch has been updated and the GitHub Action to build and pu
To test these images against our API test suite, go to the "alpha" workflow at https://github.com/gdcc/api-test-runner/actions/workflows/alpha.yml and run it.
-If there are failures, additional dependencies or settings may have been added to the "develop" workflow. Copy them over and try again.
+Don't be surprised if there are failures. The test runner is a work in progress! Additional dependencies or settings may have been added to the "develop" workflow. Copy them over and try again.
.. _build-guides:
@@ -186,11 +236,6 @@ Upload the following artifacts to the draft release you created:
- metadata block tsv files
- config files
-Deploy on Demo
---------------
-
-Now that you have the release ready to go, consider giving it one final test by deploying it on https://demo.dataverse.org. Note that this is also an opportunity to re-test the upgrade checklist as described in the release note.
-
Publish the Release
-------------------
@@ -228,7 +273,12 @@ Create a new branch (any name is fine but ``prepare-next-iteration`` is suggeste
Now create a pull request and merge it.
-For more background, see :ref:`base-supported-image-tags`.
+For more background, see :ref:`base-supported-image-tags`. For an example, see https://github.com/IQSS/dataverse/pull/10896
+
+Deploy Final Release on Demo
+----------------------------
+
+Above you already did the hard work of deploying a release candidate to https://demo.dataverse.org. It should be relatively straighforward to undeploy the release candidate and deploy the final release.
Add the Release to the Dataverse Roadmap
----------------------------------------
From 7f8f298c600782c15ae4374c6623c2a7e18ea4af Mon Sep 17 00:00:00 2001
From: Philip Durbin
Date: Wed, 16 Oct 2024 17:00:16 -0400
Subject: [PATCH 049/270] typo #10931
---
doc/sphinx-guides/source/developers/making-releases.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/sphinx-guides/source/developers/making-releases.rst b/doc/sphinx-guides/source/developers/making-releases.rst
index 2afdfd2eb2f..350f1fdcaf3 100755
--- a/doc/sphinx-guides/source/developers/making-releases.rst
+++ b/doc/sphinx-guides/source/developers/making-releases.rst
@@ -278,7 +278,7 @@ For more background, see :ref:`base-supported-image-tags`. For an example, see h
Deploy Final Release on Demo
----------------------------
-Above you already did the hard work of deploying a release candidate to https://demo.dataverse.org. It should be relatively straighforward to undeploy the release candidate and deploy the final release.
+Above you already did the hard work of deploying a release candidate to https://demo.dataverse.org. It should be relatively straightforward to undeploy the release candidate and deploy the final release.
Add the Release to the Dataverse Roadmap
----------------------------------------
From 330462e16a7b231cb5894fbb0f04fd8fd6cb2701 Mon Sep 17 00:00:00 2001
From: qqmyers
Date: Wed, 16 Oct 2024 17:01:15 -0400
Subject: [PATCH 050/270] Additional DataCiteXML testing with more fields
---
.../doi/datacite/XmlMetadataTemplateTest.java | 289 +++++++++++++++++-
1 file changed, 287 insertions(+), 2 deletions(-)
diff --git a/src/test/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/XmlMetadataTemplateTest.java b/src/test/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/XmlMetadataTemplateTest.java
index c03146904de..f282e681175 100644
--- a/src/test/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/XmlMetadataTemplateTest.java
+++ b/src/test/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/XmlMetadataTemplateTest.java
@@ -1,15 +1,21 @@
package edu.harvard.iq.dataverse.pidproviders.doi.datacite;
+import edu.harvard.iq.dataverse.ControlledVocabularyValue;
+import edu.harvard.iq.dataverse.DataCitation;
import edu.harvard.iq.dataverse.Dataset;
import edu.harvard.iq.dataverse.DatasetAuthor;
import edu.harvard.iq.dataverse.DatasetField;
+import edu.harvard.iq.dataverse.DatasetFieldCompoundValue;
import edu.harvard.iq.dataverse.DatasetFieldConstant;
import edu.harvard.iq.dataverse.DatasetFieldType;
+import edu.harvard.iq.dataverse.DatasetFieldValue;
import edu.harvard.iq.dataverse.DatasetFieldType.FieldType;
import edu.harvard.iq.dataverse.DatasetVersion;
import edu.harvard.iq.dataverse.DatasetVersion.VersionState;
+import edu.harvard.iq.dataverse.Dataverse;
import edu.harvard.iq.dataverse.DataverseServiceBean;
import edu.harvard.iq.dataverse.GlobalId;
+import edu.harvard.iq.dataverse.MetadataBlock;
import edu.harvard.iq.dataverse.TermsOfUseAndAccess;
import edu.harvard.iq.dataverse.branding.BrandingUtil;
import edu.harvard.iq.dataverse.dataset.DatasetType;
@@ -20,16 +26,30 @@
import edu.harvard.iq.dataverse.settings.JvmSettings;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
import edu.harvard.iq.dataverse.util.SystemConfig;
+import edu.harvard.iq.dataverse.util.json.CompoundVocabularyException;
+import edu.harvard.iq.dataverse.util.json.ControlledVocabularyException;
+import edu.harvard.iq.dataverse.util.json.JsonParseException;
+import edu.harvard.iq.dataverse.util.json.JsonParser;
+import edu.harvard.iq.dataverse.util.json.JsonUtil;
import edu.harvard.iq.dataverse.util.testing.JvmSetting;
import edu.harvard.iq.dataverse.util.testing.LocalJvmSettings;
import edu.harvard.iq.dataverse.util.xml.XmlValidator;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonString;
+import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Paths;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import javax.xml.transform.stream.StreamSource;
@@ -72,7 +92,7 @@ public static void setupMocks() {
}
- /**
+ /** A minimal example to assure that the XMLMetadataTemplate generates output consistent with the DataCite XML v4.5 schema.
*/
@Test
public void testDataCiteXMLCreation() throws IOException {
@@ -135,7 +155,61 @@ public void testDataCiteXMLCreation() throws IOException {
d.setDatasetType(dType);
String xml = template.generateXML(d);
- System.out.println("Output is " + xml);
+ System.out.println("Output from minimal example is " + xml);
+ try {
+ StreamSource source = new StreamSource(new StringReader(xml));
+ source.setSystemId("DataCite XML for test dataset");
+ assertTrue(XmlValidator.validateXmlSchema(source, new URL("https://schema.datacite.org/meta/kernel-4/metadata.xsd")));
+ } catch (SAXException e) {
+ System.out.println("Invalid schema: " + e.getMessage());
+ }
+
+ }
+
+ /**
+ * This tests a more complete example based off of the dataset-all-defaults
+ * file, again checking for conformance of the result with the DataCite XML v4.5
+ * schema.
+ */
+ @Test
+ public void testDataCiteXMLCreationAllFields() throws IOException {
+ Dataverse collection = new Dataverse();
+ collection.setCitationDatasetFieldTypes(new ArrayList<>());
+ Dataset d = new Dataset();
+ d.setOwner(collection);
+ DatasetVersion dv = new DatasetVersion();
+ TermsOfUseAndAccess toa = new TermsOfUseAndAccess();
+ toa.setTermsOfUse("Some terms");
+ dv.setTermsOfUseAndAccess(toa);
+ dv.setDataset(d);
+ DatasetFieldType primitiveDSFType = new DatasetFieldType(DatasetFieldConstant.title,
+ DatasetFieldType.FieldType.TEXT, false);
+ DatasetField testDatasetField = new DatasetField();
+
+ dv.setVersionState(VersionState.DRAFT);
+
+ testDatasetField.setDatasetVersion(dv);
+
+ File datasetVersionJson = new File("src/test/java/edu/harvard/iq/dataverse/export/dataset-all-defaults.txt");
+ String datasetVersionAsJson = new String(Files.readAllBytes(Paths.get(datasetVersionJson.getAbsolutePath())));
+ JsonObject datasetJson = JsonUtil.getJsonObject(datasetVersionAsJson);
+
+ GlobalId doi = new GlobalId("doi", datasetJson.getString("authority"), datasetJson.getString("identifier"), null, null, null);
+ d.setGlobalId(doi);
+
+
+ List fields = assertDoesNotThrow(() -> XmlMetadataTemplateTest.parseMetadataBlocks(datasetJson.getJsonObject("datasetVersion").getJsonObject("metadataBlocks")));
+ dv.setDatasetFields(fields);
+
+ ArrayList dsvs = new ArrayList<>();
+ dsvs.add(0, dv);
+ d.setVersions(dsvs);
+ DatasetType dType = new DatasetType();
+ dType.setName(DatasetType.DATASET_TYPE_DATASET);
+ d.setDatasetType(dType);
+ String xml = DOIDataCiteRegisterService.getMetadataFromDvObject(
+ dv.getDataset().getGlobalId().asString(), new DataCitation(dv).getDataCiteMetadata(), dv.getDataset());
+ System.out.println("Output from dataset-all-defaults is " + xml);
try {
StreamSource source = new StreamSource(new StringReader(xml));
source.setSystemId("DataCite XML for test dataset");
@@ -146,4 +220,215 @@ public void testDataCiteXMLCreation() throws IOException {
}
+
+ /**
+ * Mock Utility Methods - These methods support importing DatasetFields from the
+ * Dataverse JSON export format. They assume that any DatasetFieldType
+ * referenced exists, that any Controlled Vocabulary value exists, etc. which
+ * avoids having to do database lookups or read metadatablock tsv files. They
+ * are derived from the JsonParser methods of the same names with any db
+ * references and DatasetFieldType-related error checking removed.
+ */
+ public static List parseMetadataBlocks(JsonObject json) throws JsonParseException {
+
+ Map existingTypes = new HashMap<>();
+
+ Set keys = json.keySet();
+ List fields = new LinkedList<>();
+
+ for (String blockName : keys) {
+ MetadataBlock block = new MetadataBlock();
+ block.setName(blockName);
+ JsonObject blockJson = json.getJsonObject(blockName);
+ JsonArray fieldsJson = blockJson.getJsonArray("fields");
+ fields.addAll(parseFieldsFromArray(fieldsJson, true, block, existingTypes));
+ }
+ return fields;
+ }
+
+ private static List parseFieldsFromArray(JsonArray fieldsArray, Boolean testType, MetadataBlock block,
+ Map existingTypes) throws JsonParseException {
+ List fields = new LinkedList<>();
+ for (JsonObject fieldJson : fieldsArray.getValuesAs(JsonObject.class)) {
+
+ DatasetField field = parseField(fieldJson, testType, block, existingTypes);
+ if (field != null) {
+ fields.add(field);
+ }
+
+ }
+ return fields;
+
+ }
+
+
+ public static DatasetField parseField(JsonObject json, Boolean testType, MetadataBlock block, Map existingTypes) throws JsonParseException {
+ if (json == null) {
+ return null;
+ }
+
+ DatasetField ret = new DatasetField();
+ String fieldName = json.getString("typeName", "");
+ String typeClass = json.getString("typeClass", "");
+ if(!existingTypes.containsKey(fieldName)) {
+ boolean multiple = json.getBoolean("multiple");
+ DatasetFieldType fieldType = new DatasetFieldType();
+ fieldType.setName(fieldName);
+ fieldType.setAllowMultiples(multiple);
+ fieldType.setAllowControlledVocabulary(typeClass.equals("controlledVocabulary"));
+ fieldType.setFieldType(FieldType.TEXT);
+ fieldType.setMetadataBlock(block);
+ fieldType.setChildDatasetFieldTypes(new ArrayList<>());
+ existingTypes.put(fieldName, fieldType);
+ }
+ DatasetFieldType type = existingTypes.get(fieldName);
+ ret.setDatasetFieldType(type);
+
+ if (typeClass.equals("compound")) {
+ parseCompoundValue(ret, type, json, testType, block, existingTypes);
+ } else if (type.isControlledVocabulary()) {
+ parseControlledVocabularyValue(ret, type, json);
+ } else {
+ parsePrimitiveValue(ret, type, json);
+ }
+
+ return ret;
+ }
+
+ public static void parseCompoundValue(DatasetField dsf, DatasetFieldType compoundType, JsonObject json, Boolean testType, MetadataBlock block, Map existingTypes) throws JsonParseException {
+ List vocabExceptions = new ArrayList<>();
+ List vals = new LinkedList<>();
+ if (compoundType.isAllowMultiples()) {
+ int order = 0;
+ try {
+ json.getJsonArray("value").getValuesAs(JsonObject.class);
+ } catch (ClassCastException cce) {
+ throw new JsonParseException("Invalid values submitted for " + compoundType.getName() + ". It should be an array of values.");
+ }
+ for (JsonObject obj : json.getJsonArray("value").getValuesAs(JsonObject.class)) {
+ DatasetFieldCompoundValue cv = new DatasetFieldCompoundValue();
+ List fields = new LinkedList<>();
+ for (String fieldName : obj.keySet()) {
+ JsonObject childFieldJson = obj.getJsonObject(fieldName);
+ DatasetField f=null;
+ try {
+ f = parseField(childFieldJson, testType, block, existingTypes);
+ } catch(ControlledVocabularyException ex) {
+ vocabExceptions.add(ex);
+ }
+
+ if (f!=null) {
+ f.setParentDatasetFieldCompoundValue(cv);
+ fields.add(f);
+ }
+ }
+ if (!fields.isEmpty()) {
+ cv.setChildDatasetFields(fields);
+ cv.setDisplayOrder(order);
+ vals.add(cv);
+ }
+ order++;
+ }
+
+
+
+ } else {
+
+ DatasetFieldCompoundValue cv = new DatasetFieldCompoundValue();
+ List fields = new LinkedList<>();
+ JsonObject value = json.getJsonObject("value");
+ for (String key : value.keySet()) {
+ JsonObject childFieldJson = value.getJsonObject(key);
+ DatasetField f = null;
+ try {
+ f=parseField(childFieldJson, testType, block, existingTypes);
+ } catch(ControlledVocabularyException ex ) {
+ vocabExceptions.add(ex);
+ }
+ if (f!=null) {
+ f.setParentDatasetFieldCompoundValue(cv);
+ fields.add(f);
+ }
+ }
+ if (!fields.isEmpty()) {
+ cv.setChildDatasetFields(fields);
+ vals.add(cv);
+ }
+
+ }
+ if (!vocabExceptions.isEmpty()) {
+ throw new CompoundVocabularyException( "Invalid controlled vocabulary in compound field ", vocabExceptions, vals);
+ }
+
+ for (DatasetFieldCompoundValue dsfcv : vals) {
+ dsfcv.setParentDatasetField(dsf);
+ }
+ dsf.setDatasetFieldCompoundValues(vals);
+ }
+
+ public static void parsePrimitiveValue(DatasetField dsf, DatasetFieldType dft , JsonObject json) throws JsonParseException {
+ List vals = new LinkedList<>();
+ if (dft.isAllowMultiples()) {
+ try {
+ json.getJsonArray("value").getValuesAs(JsonObject.class);
+ } catch (ClassCastException cce) {
+ throw new JsonParseException("Invalid values submitted for " + dft.getName() + ". It should be an array of values.");
+ }
+ for (JsonString val : json.getJsonArray("value").getValuesAs(JsonString.class)) {
+ DatasetFieldValue datasetFieldValue = new DatasetFieldValue(dsf);
+ datasetFieldValue.setDisplayOrder(vals.size() - 1);
+ datasetFieldValue.setValue(val.getString().trim());
+ vals.add(datasetFieldValue);
+ }
+
+ } else {
+ try {json.getString("value");}
+ catch (ClassCastException cce) {
+ throw new JsonParseException("Invalid value submitted for " + dft.getName() + ". It should be a single value.");
+ }
+ DatasetFieldValue datasetFieldValue = new DatasetFieldValue();
+ datasetFieldValue.setValue(json.getString("value", "").trim());
+ datasetFieldValue.setDatasetField(dsf);
+ vals.add(datasetFieldValue);
+ }
+
+ dsf.setDatasetFieldValues(vals);
+ }
+
+ public static void parseControlledVocabularyValue(DatasetField dsf, DatasetFieldType cvvType, JsonObject json) throws JsonParseException {
+ List vals = new LinkedList<>();
+ try {
+ if (cvvType.isAllowMultiples()) {
+ try {
+ json.getJsonArray("value").getValuesAs(JsonObject.class);
+ } catch (ClassCastException cce) {
+ throw new JsonParseException("Invalid values submitted for " + cvvType.getName() + ". It should be an array of values.");
+ }
+ for (JsonString strVal : json.getJsonArray("value").getValuesAs(JsonString.class)) {
+ String strValue = strVal.getString();
+ ControlledVocabularyValue cvv = new ControlledVocabularyValue();
+ cvv.setDatasetFieldType(cvvType);
+ cvv.setStrValue(strVal.getString());
+ vals.add(cvv);
+ }
+
+ } else {
+ try {
+ json.getString("value");
+ } catch (ClassCastException cce) {
+ throw new JsonParseException("Invalid value submitted for " + cvvType.getName() + ". It should be a single value.");
+ }
+ String strValue = json.getString("value", "");
+ ControlledVocabularyValue cvv = new ControlledVocabularyValue();
+ cvv.setDatasetFieldType(cvvType);
+ cvv.setStrValue(strValue);
+ vals.add(cvv);
+ }
+ } catch (ClassCastException cce) {
+ throw new JsonParseException("Invalid values submitted for " + cvvType.getName());
+ }
+
+ dsf.setControlledVocabularyValues(vals);
+ }
+
}
From 816b7047e4f6209c112c6f2bba3c9b22d05a1ca4 Mon Sep 17 00:00:00 2001
From: qqmyers
Date: Wed, 16 Oct 2024 17:08:13 -0400
Subject: [PATCH 051/270] formatting after review dog notice
---
.../doi/datacite/XmlMetadataTemplateTest.java | 113 ++++++++++--------
1 file changed, 63 insertions(+), 50 deletions(-)
diff --git a/src/test/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/XmlMetadataTemplateTest.java b/src/test/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/XmlMetadataTemplateTest.java
index f282e681175..2bd6818821d 100644
--- a/src/test/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/XmlMetadataTemplateTest.java
+++ b/src/test/java/edu/harvard/iq/dataverse/pidproviders/doi/datacite/XmlMetadataTemplateTest.java
@@ -92,7 +92,9 @@ public static void setupMocks() {
}
- /** A minimal example to assure that the XMLMetadataTemplate generates output consistent with the DataCite XML v4.5 schema.
+ /**
+ * A minimal example to assure that the XMLMetadataTemplate generates output
+ * consistent with the DataCite XML v4.5 schema.
*/
@Test
public void testDataCiteXMLCreation() throws IOException {
@@ -126,7 +128,7 @@ public void testDataCiteXMLCreation() throws IOException {
doiMetadata.setAuthors(authors);
doiMetadata.setPublisher("Dataverse");
XmlMetadataTemplate template = new XmlMetadataTemplate(doiMetadata);
-
+
Dataset d = new Dataset();
GlobalId doi = new GlobalId("doi", "10.5072", "FK2/ABCDEF", null, null, null);
d.setGlobalId(doi);
@@ -159,11 +161,12 @@ public void testDataCiteXMLCreation() throws IOException {
try {
StreamSource source = new StreamSource(new StringReader(xml));
source.setSystemId("DataCite XML for test dataset");
- assertTrue(XmlValidator.validateXmlSchema(source, new URL("https://schema.datacite.org/meta/kernel-4/metadata.xsd")));
+ assertTrue(XmlValidator.validateXmlSchema(source,
+ new URL("https://schema.datacite.org/meta/kernel-4/metadata.xsd")));
} catch (SAXException e) {
System.out.println("Invalid schema: " + e.getMessage());
}
-
+
}
/**
@@ -189,38 +192,39 @@ public void testDataCiteXMLCreationAllFields() throws IOException {
dv.setVersionState(VersionState.DRAFT);
testDatasetField.setDatasetVersion(dv);
-
+
File datasetVersionJson = new File("src/test/java/edu/harvard/iq/dataverse/export/dataset-all-defaults.txt");
String datasetVersionAsJson = new String(Files.readAllBytes(Paths.get(datasetVersionJson.getAbsolutePath())));
JsonObject datasetJson = JsonUtil.getJsonObject(datasetVersionAsJson);
-
- GlobalId doi = new GlobalId("doi", datasetJson.getString("authority"), datasetJson.getString("identifier"), null, null, null);
+
+ GlobalId doi = new GlobalId("doi", datasetJson.getString("authority"), datasetJson.getString("identifier"),
+ null, null, null);
d.setGlobalId(doi);
-
- List fields = assertDoesNotThrow(() -> XmlMetadataTemplateTest.parseMetadataBlocks(datasetJson.getJsonObject("datasetVersion").getJsonObject("metadataBlocks")));
+ List fields = assertDoesNotThrow(() -> XmlMetadataTemplateTest
+ .parseMetadataBlocks(datasetJson.getJsonObject("datasetVersion").getJsonObject("metadataBlocks")));
dv.setDatasetFields(fields);
-
+
ArrayList dsvs = new ArrayList<>();
dsvs.add(0, dv);
d.setVersions(dsvs);
DatasetType dType = new DatasetType();
dType.setName(DatasetType.DATASET_TYPE_DATASET);
d.setDatasetType(dType);
- String xml = DOIDataCiteRegisterService.getMetadataFromDvObject(
- dv.getDataset().getGlobalId().asString(), new DataCitation(dv).getDataCiteMetadata(), dv.getDataset());
+ String xml = DOIDataCiteRegisterService.getMetadataFromDvObject(dv.getDataset().getGlobalId().asString(),
+ new DataCitation(dv).getDataCiteMetadata(), dv.getDataset());
System.out.println("Output from dataset-all-defaults is " + xml);
try {
StreamSource source = new StreamSource(new StringReader(xml));
source.setSystemId("DataCite XML for test dataset");
- assertTrue(XmlValidator.validateXmlSchema(source, new URL("https://schema.datacite.org/meta/kernel-4/metadata.xsd")));
+ assertTrue(XmlValidator.validateXmlSchema(source,
+ new URL("https://schema.datacite.org/meta/kernel-4/metadata.xsd")));
} catch (SAXException e) {
System.out.println("Invalid schema: " + e.getMessage());
}
-
+
}
-
/**
* Mock Utility Methods - These methods support importing DatasetFields from the
* Dataverse JSON export format. They assume that any DatasetFieldType
@@ -230,9 +234,9 @@ public void testDataCiteXMLCreationAllFields() throws IOException {
* references and DatasetFieldType-related error checking removed.
*/
public static List parseMetadataBlocks(JsonObject json) throws JsonParseException {
-
+
Map existingTypes = new HashMap<>();
-
+
Set keys = json.keySet();
List fields = new LinkedList<>();
@@ -259,10 +263,10 @@ private static List parseFieldsFromArray(JsonArray fieldsArray, Bo
}
return fields;
- }
+ }
-
- public static DatasetField parseField(JsonObject json, Boolean testType, MetadataBlock block, Map existingTypes) throws JsonParseException {
+ public static DatasetField parseField(JsonObject json, Boolean testType, MetadataBlock block,
+ Map existingTypes) throws JsonParseException {
if (json == null) {
return null;
}
@@ -270,7 +274,7 @@ public static DatasetField parseField(JsonObject json, Boolean testType, Metad
DatasetField ret = new DatasetField();
String fieldName = json.getString("typeName", "");
String typeClass = json.getString("typeClass", "");
- if(!existingTypes.containsKey(fieldName)) {
+ if (!existingTypes.containsKey(fieldName)) {
boolean multiple = json.getBoolean("multiple");
DatasetFieldType fieldType = new DatasetFieldType();
fieldType.setName(fieldName);
@@ -294,8 +298,10 @@ public static DatasetField parseField(JsonObject json, Boolean testType, Metad
return ret;
}
-
- public static void parseCompoundValue(DatasetField dsf, DatasetFieldType compoundType, JsonObject json, Boolean testType, MetadataBlock block, Map existingTypes) throws JsonParseException {
+
+ public static void parseCompoundValue(DatasetField dsf, DatasetFieldType compoundType, JsonObject json,
+ Boolean testType, MetadataBlock block, Map existingTypes)
+ throws JsonParseException {
List vocabExceptions = new ArrayList<>();
List vals = new LinkedList<>();
if (compoundType.isAllowMultiples()) {
@@ -303,23 +309,24 @@ public static void parseCompoundValue(DatasetField dsf, DatasetFieldType compoun
try {
json.getJsonArray("value").getValuesAs(JsonObject.class);
} catch (ClassCastException cce) {
- throw new JsonParseException("Invalid values submitted for " + compoundType.getName() + ". It should be an array of values.");
+ throw new JsonParseException("Invalid values submitted for " + compoundType.getName()
+ + ". It should be an array of values.");
}
for (JsonObject obj : json.getJsonArray("value").getValuesAs(JsonObject.class)) {
DatasetFieldCompoundValue cv = new DatasetFieldCompoundValue();
List fields = new LinkedList<>();
for (String fieldName : obj.keySet()) {
JsonObject childFieldJson = obj.getJsonObject(fieldName);
- DatasetField f=null;
+ DatasetField f = null;
try {
f = parseField(childFieldJson, testType, block, existingTypes);
- } catch(ControlledVocabularyException ex) {
+ } catch (ControlledVocabularyException ex) {
vocabExceptions.add(ex);
}
-
- if (f!=null) {
+
+ if (f != null) {
f.setParentDatasetFieldCompoundValue(cv);
- fields.add(f);
+ fields.add(f);
}
}
if (!fields.isEmpty()) {
@@ -330,10 +337,8 @@ public static void parseCompoundValue(DatasetField dsf, DatasetFieldType compoun
order++;
}
-
-
} else {
-
+
DatasetFieldCompoundValue cv = new DatasetFieldCompoundValue();
List fields = new LinkedList<>();
JsonObject value = json.getJsonObject("value");
@@ -341,11 +346,11 @@ public static void parseCompoundValue(DatasetField dsf, DatasetFieldType compoun
JsonObject childFieldJson = value.getJsonObject(key);
DatasetField f = null;
try {
- f=parseField(childFieldJson, testType, block, existingTypes);
- } catch(ControlledVocabularyException ex ) {
+ f = parseField(childFieldJson, testType, block, existingTypes);
+ } catch (ControlledVocabularyException ex) {
vocabExceptions.add(ex);
}
- if (f!=null) {
+ if (f != null) {
f.setParentDatasetFieldCompoundValue(cv);
fields.add(f);
}
@@ -354,10 +359,11 @@ public static void parseCompoundValue(DatasetField dsf, DatasetFieldType compoun
cv.setChildDatasetFields(fields);
vals.add(cv);
}
-
- }
+
+ }
if (!vocabExceptions.isEmpty()) {
- throw new CompoundVocabularyException( "Invalid controlled vocabulary in compound field ", vocabExceptions, vals);
+ throw new CompoundVocabularyException("Invalid controlled vocabulary in compound field ", vocabExceptions,
+ vals);
}
for (DatasetFieldCompoundValue dsfcv : vals) {
@@ -366,13 +372,15 @@ public static void parseCompoundValue(DatasetField dsf, DatasetFieldType compoun
dsf.setDatasetFieldCompoundValues(vals);
}
- public static void parsePrimitiveValue(DatasetField dsf, DatasetFieldType dft , JsonObject json) throws JsonParseException {
+ public static void parsePrimitiveValue(DatasetField dsf, DatasetFieldType dft, JsonObject json)
+ throws JsonParseException {
List vals = new LinkedList<>();
if (dft.isAllowMultiples()) {
- try {
- json.getJsonArray("value").getValuesAs(JsonObject.class);
+ try {
+ json.getJsonArray("value").getValuesAs(JsonObject.class);
} catch (ClassCastException cce) {
- throw new JsonParseException("Invalid values submitted for " + dft.getName() + ". It should be an array of values.");
+ throw new JsonParseException(
+ "Invalid values submitted for " + dft.getName() + ". It should be an array of values.");
}
for (JsonString val : json.getJsonArray("value").getValuesAs(JsonString.class)) {
DatasetFieldValue datasetFieldValue = new DatasetFieldValue(dsf);
@@ -382,10 +390,12 @@ public static void parsePrimitiveValue(DatasetField dsf, DatasetFieldType dft ,
}
} else {
- try {json.getString("value");}
- catch (ClassCastException cce) {
- throw new JsonParseException("Invalid value submitted for " + dft.getName() + ". It should be a single value.");
- }
+ try {
+ json.getString("value");
+ } catch (ClassCastException cce) {
+ throw new JsonParseException(
+ "Invalid value submitted for " + dft.getName() + ". It should be a single value.");
+ }
DatasetFieldValue datasetFieldValue = new DatasetFieldValue();
datasetFieldValue.setValue(json.getString("value", "").trim());
datasetFieldValue.setDatasetField(dsf);
@@ -394,15 +404,17 @@ public static void parsePrimitiveValue(DatasetField dsf, DatasetFieldType dft ,
dsf.setDatasetFieldValues(vals);
}
-
- public static void parseControlledVocabularyValue(DatasetField dsf, DatasetFieldType cvvType, JsonObject json) throws JsonParseException {
+
+ public static void parseControlledVocabularyValue(DatasetField dsf, DatasetFieldType cvvType, JsonObject json)
+ throws JsonParseException {
List vals = new LinkedList<>();
try {
if (cvvType.isAllowMultiples()) {
try {
json.getJsonArray("value").getValuesAs(JsonObject.class);
} catch (ClassCastException cce) {
- throw new JsonParseException("Invalid values submitted for " + cvvType.getName() + ". It should be an array of values.");
+ throw new JsonParseException(
+ "Invalid values submitted for " + cvvType.getName() + ". It should be an array of values.");
}
for (JsonString strVal : json.getJsonArray("value").getValuesAs(JsonString.class)) {
String strValue = strVal.getString();
@@ -416,7 +428,8 @@ public static void parseControlledVocabularyValue(DatasetField dsf, DatasetField
try {
json.getString("value");
} catch (ClassCastException cce) {
- throw new JsonParseException("Invalid value submitted for " + cvvType.getName() + ". It should be a single value.");
+ throw new JsonParseException(
+ "Invalid value submitted for " + cvvType.getName() + ". It should be a single value.");
}
String strValue = json.getString("value", "");
ControlledVocabularyValue cvv = new ControlledVocabularyValue();
From 9285c926e90ca7fec98777654630a36eb89550f7 Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Thu, 17 Oct 2024 10:44:53 -0400
Subject: [PATCH 052/270] Update dataset-management.rst
---
.../source/user/dataset-management.rst | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/doc/sphinx-guides/source/user/dataset-management.rst b/doc/sphinx-guides/source/user/dataset-management.rst
index 2e5d84748a8..39fe0128dfd 100755
--- a/doc/sphinx-guides/source/user/dataset-management.rst
+++ b/doc/sphinx-guides/source/user/dataset-management.rst
@@ -169,7 +169,7 @@ Certain file types in the Dataverse installation are supported by additional fun
File Previews
-------------
-Dataverse installations can add previewers for common file types uploaded by their research communities. The previews appear on the file page. If a preview tool for a specific file type is available, the preview will be created and will display automatically, after terms have been agreed to or a guestbook entry has been made, if necessary. File previews are not available for restricted files unless they are being accessed using a Private URL. See also :ref:`privateurl`.
+Dataverse installations can add previewers for common file types uploaded by their research communities. The previews appear on the file page. If a preview tool for a specific file type is available, the preview will be created and will display automatically, after terms have been agreed to or a guestbook entry has been made, if necessary. File previews are not available for restricted files unless they are being accessed using a Preview URL. See also :ref:`privateurl`.
Previewers are available for the following file types:
@@ -678,21 +678,21 @@ If you have a Contributor role (can edit metadata, upload files, and edit files,
.. _privateurl:
-Private URL to Review Unpublished Dataset
+Preview URL to Review Unpublished Dataset
=========================================
-Creating a Private URL for your dataset allows you to share your dataset (for viewing and downloading of files) before it is published to a wide group of individuals who may not have a user account on the Dataverse installation. Anyone you send the Private URL to will not have to log into the Dataverse installation to view the dataset.
+Creating a Preview URL for your dataset allows you to share your dataset (for viewing and downloading of files) before it is published to a wide group of individuals who may not have a user account on the Dataverse installation. Anyone you send the Preview URL to will not have to log into the Dataverse installation to view the dataset.
-**Note:** To create a Private URL, you must have the *ManageDatasetPermissions* permission for your dataset, usually given by the :ref:`roles ` *Curator* or *Administrator*.
+**Note:** To create a Preview URL, you must have the *ManageDatasetPermissions* permission for your dataset, usually given by the :ref:`roles ` *Curator* or *Administrator*.
#. Go to your unpublished dataset
#. Select the “Edit” button
-#. Select “Private URL” in the dropdown menu
-#. In the pop-up select “Create Private URL” or "Create URL for Anonymized Access". The latter supports anonymous review by removing author names and other potentially identifying information from citations, version history tables, and some metadata fields (as configured by the administrator).
-#. Copy the Private URL which has been created for this dataset and it can now be shared with anyone you wish to have access to view or download files in your unpublished dataset.
+#. Select “Preview URL” in the dropdown menu
+#. In the pop-up select “Create General Preview URL” or "Create URL for Anonymized Access". The latter supports anonymous review by removing author names and other potentially identifying information from citations, version history tables, and some metadata fields (as configured by the administrator).
+#. Copy the Preview URL which has been created for this dataset and it can now be shared with anyone you wish to have access to view or download files in your unpublished dataset.
-To disable a Private URL and to revoke access, follow the same steps as above until step #3 when you return to the popup, click the “Disable Private URL” button.
-Note that only one PrivateURL (normal or with anonymized access) can be configured per dataset at a time.
+To disable a Preview URL and to revoke access, follow the same steps as above until step #3 when you return to the popup, click the “Disable Preview URL” button.
+Note that only one Preview URL (normal or with anonymized access) can be configured per dataset at a time.
Embargoes
=========
From 8af5b1c8ae262d9cdc05756e01f8f8ba1e76166b Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Thu, 17 Oct 2024 14:43:54 -0400
Subject: [PATCH 053/270] #8184 add updated api endpoints - deprecate private
url
---
.../harvard/iq/dataverse/api/Datasets.java | 43 +++++++++++++++++++
1 file changed, 43 insertions(+)
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
index 369a22fe8d7..3eee734877b 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
@@ -2171,6 +2171,7 @@ public Response getAssignments(@Context ContainerRequestContext crc, @PathParam(
@GET
@AuthRequired
+ @Deprecated(forRemoval = true, since = "2024-10-17")
@Path("{id}/privateUrl")
public Response getPrivateUrlData(@Context ContainerRequestContext crc, @PathParam("id") String idSupplied) {
return response( req -> {
@@ -2182,6 +2183,7 @@ public Response getPrivateUrlData(@Context ContainerRequestContext crc, @PathPar
@POST
@AuthRequired
+ @Deprecated(forRemoval = true, since = "2024-10-17")
@Path("{id}/privateUrl")
public Response createPrivateUrl(@Context ContainerRequestContext crc, @PathParam("id") String idSupplied,@DefaultValue("false") @QueryParam ("anonymizedAccess") boolean anonymizedAccess) {
if(anonymizedAccess && settingsSvc.getValueForKey(SettingsServiceBean.Key.AnonymizedFieldTypeNames)==null) {
@@ -2194,6 +2196,7 @@ public Response createPrivateUrl(@Context ContainerRequestContext crc, @PathPara
@DELETE
@AuthRequired
+ @Deprecated(forRemoval = true, since = "2024-10-17")
@Path("{id}/privateUrl")
public Response deletePrivateUrl(@Context ContainerRequestContext crc, @PathParam("id") String idSupplied) {
return response( req -> {
@@ -2207,6 +2210,46 @@ public Response deletePrivateUrl(@Context ContainerRequestContext crc, @PathPara
}
}, getRequestUser(crc));
}
+
+ @GET
+ @AuthRequired
+ @Path("{id}/previewUrl")
+ public Response getPreviewUrlData(@Context ContainerRequestContext crc, @PathParam("id") String idSupplied) {
+ return response( req -> {
+ PrivateUrl privateUrl = execCommand(new GetPrivateUrlCommand(req, findDatasetOrDie(idSupplied)));
+ return (privateUrl != null) ? ok(json(privateUrl))
+ : error(Response.Status.NOT_FOUND, "Private URL not found.");
+ }, getRequestUser(crc));
+ }
+
+ @POST
+ @AuthRequired
+ @Path("{id}/previewUrl")
+ public Response createPreviewUrl(@Context ContainerRequestContext crc, @PathParam("id") String idSupplied,@DefaultValue("false") @QueryParam ("anonymizedAccess") boolean anonymizedAccess) {
+ if(anonymizedAccess && settingsSvc.getValueForKey(SettingsServiceBean.Key.AnonymizedFieldTypeNames)==null) {
+ throw new NotAcceptableException("Anonymized Access not enabled");
+ }
+ return response(req ->
+ ok(json(execCommand(
+ new CreatePrivateUrlCommand(req, findDatasetOrDie(idSupplied), anonymizedAccess)))), getRequestUser(crc));
+ }
+
+ @DELETE
+ @AuthRequired
+ @Path("{id}/previewUrl")
+ public Response deletePreviewUrl(@Context ContainerRequestContext crc, @PathParam("id") String idSupplied) {
+ return response( req -> {
+ Dataset dataset = findDatasetOrDie(idSupplied);
+ PrivateUrl privateUrl = execCommand(new GetPrivateUrlCommand(req, dataset));
+ if (privateUrl != null) {
+ execCommand(new DeletePrivateUrlCommand(req, dataset));
+ return ok("Private URL deleted.");
+ } else {
+ return notFound("No Private URL to delete.");
+ }
+ }, getRequestUser(crc));
+ }
+
@GET
@AuthRequired
From 84e0fadf631865e08787e59428d455d1a9a86683 Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Fri, 18 Oct 2024 13:39:17 -0400
Subject: [PATCH 054/270] adding description info to the fileDsc seciton in DDI
CodeBook. #5051
---
.../export/DDIExportServiceBean.java | 38 +++++++------------
.../dataverse/export/ddi/DdiExportUtil.java | 15 ++++++++
src/main/java/propertyFiles/Bundle.properties | 4 +-
3 files changed, 30 insertions(+), 27 deletions(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/export/DDIExportServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/export/DDIExportServiceBean.java
index edd01ae98a3..d76020cb8d8 100644
--- a/src/main/java/edu/harvard/iq/dataverse/export/DDIExportServiceBean.java
+++ b/src/main/java/edu/harvard/iq/dataverse/export/DDIExportServiceBean.java
@@ -98,8 +98,10 @@ public class DDIExportServiceBean {
public static final String LEVEL_FILE = "file";
public static final String NOTE_TYPE_UNF = "VDC:UNF";
public static final String NOTE_TYPE_TAG = "DATAVERSE:TAG";
+ public static final String NOTE_TYPE_FILEDESCRIPTION = "DATAVERSE:FILEDESC";
public static final String NOTE_SUBJECT_UNF = "Universal Numeric Fingerprint";
public static final String NOTE_SUBJECT_TAG = "Data File Tag";
+ public static final String NOTE_SUBJECT_FILEDESCRIPTION = "DataFile Description";
/*
* Internal service objects:
@@ -742,11 +744,6 @@ private void createFileDscr(XMLStreamWriter xmlw, Set excludedFieldSet,
xmlw.writeEndElement(); // fileName
}
- /*
- xmlw.writeStartElement("fileCont");
- xmlw.writeCharacters( df.getContentType() );
- xmlw.writeEndElement(); // fileCont
- */
// dimensions
if (checkField("dimensns", excludedFieldSet, includedFieldSet)) {
if (dt.getCaseQuantity() != null || dt.getVarQuantity() != null || dt.getRecordsPerCase() != null) {
@@ -801,26 +798,6 @@ private void createFileDscr(XMLStreamWriter xmlw, Set excludedFieldSet,
xmlw.writeEndElement(); // notes
}
- /*
- xmlw.writeStartElement("notes");
- writeAttribute( xmlw, "type", "vdc:category" );
- xmlw.writeCharacters( fm.getCategory() );
- xmlw.writeEndElement(); // notes
- */
- // A special note for LOCKSS crawlers indicating the restricted
- // status of the file:
-
- /*
- if (tdf != null && isRestrictedFile(tdf)) {
- xmlw.writeStartElement("notes");
- writeAttribute( xmlw, "type", NOTE_TYPE_LOCKSS_CRAWL );
- writeAttribute( xmlw, "level", LEVEL_FILE );
- writeAttribute( xmlw, "subject", NOTE_SUBJECT_LOCKSS_PERM );
- xmlw.writeCharacters( "restricted" );
- xmlw.writeEndElement(); // notes
-
- }
- */
if (checkField("tags", excludedFieldSet, includedFieldSet) && df.getTags() != null) {
for (int i = 0; i < df.getTags().size(); i++) {
xmlw.writeStartElement("notes");
@@ -831,6 +808,17 @@ private void createFileDscr(XMLStreamWriter xmlw, Set excludedFieldSet,
xmlw.writeEndElement(); // notes
}
}
+
+ // A dedicated node for the Description entry
+ if (!StringUtilisEmpty(fm.getDescription())) {
+ xmlw.writeStartElement("notes");
+ xmlw.writeAttribute("level", LEVEL_FILE);
+ xmlw.writeAttribute("type", NOTE_TYPE_FILEDESCRIPTION);
+ xmlw.writeAttribute("subject", NOTE_SUBJECT_FILEDESCRIPTION);
+ xmlw.writeCharacters(fm.getDescription());
+ xmlw.writeEndElement(); // notes
+ }
+
xmlw.writeEndElement(); // fileDscr
}
diff --git a/src/main/java/edu/harvard/iq/dataverse/export/ddi/DdiExportUtil.java b/src/main/java/edu/harvard/iq/dataverse/export/ddi/DdiExportUtil.java
index f5efc448090..05ddbe83e78 100644
--- a/src/main/java/edu/harvard/iq/dataverse/export/ddi/DdiExportUtil.java
+++ b/src/main/java/edu/harvard/iq/dataverse/export/ddi/DdiExportUtil.java
@@ -14,8 +14,10 @@
import static edu.harvard.iq.dataverse.export.DDIExportServiceBean.LEVEL_FILE;
import static edu.harvard.iq.dataverse.export.DDIExportServiceBean.NOTE_SUBJECT_TAG;
import static edu.harvard.iq.dataverse.export.DDIExportServiceBean.NOTE_SUBJECT_UNF;
+import static edu.harvard.iq.dataverse.export.DDIExportServiceBean.NOTE_SUBJECT_FILEDESCRIPTION;
import static edu.harvard.iq.dataverse.export.DDIExportServiceBean.NOTE_TYPE_TAG;
import static edu.harvard.iq.dataverse.export.DDIExportServiceBean.NOTE_TYPE_UNF;
+import static edu.harvard.iq.dataverse.export.DDIExportServiceBean.NOTE_TYPE_FILEDESCRIPTION;
import edu.harvard.iq.dataverse.export.DDIExporter;
import edu.harvard.iq.dataverse.pidproviders.PidUtil;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
@@ -1901,6 +1903,8 @@ private static void createFileDscr(XMLStreamWriter xmlw, JsonArray fileDetails)
xmlw.writeEndElement(); // notes
}
+ // If any tabular tags are present, each is formatted in a
+ // dedicated note:
if (fileJson.containsKey("tabularTags")) {
JsonArray tags = fileJson.getJsonArray("tabularTags");
for (int j = 0; j < tags.size(); j++) {
@@ -1912,6 +1916,17 @@ private static void createFileDscr(XMLStreamWriter xmlw, JsonArray fileDetails)
xmlw.writeEndElement(); // notes
}
}
+
+ // Adding a dedicated node for the description entry (for
+ // non-tabular files we format it under the field)
+ if (fileJson.containsKey("description")) {
+ xmlw.writeStartElement("notes");
+ xmlw.writeAttribute("level", LEVEL_FILE);
+ xmlw.writeAttribute("type", NOTE_TYPE_FILEDESCRIPTION);
+ xmlw.writeAttribute("subject", NOTE_SUBJECT_FILEDESCRIPTION);
+ xmlw.writeCharacters(fileJson.getString("description"));
+ xmlw.writeEndElement(); // notes
+ }
// TODO: add the remaining fileDscr elements!
xmlw.writeEndElement(); // fileDscr
diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties
index 5f3e4c33e0b..a355bcc379f 100644
--- a/src/main/java/propertyFiles/Bundle.properties
+++ b/src/main/java/propertyFiles/Bundle.properties
@@ -1464,7 +1464,7 @@ dataset.editBtn.itemLabel.deleteDataset=Delete Dataset
dataset.editBtn.itemLabel.deleteDraft=Delete Draft Version
dataset.editBtn.itemLabel.deaccession=Deaccession Dataset
dataset.exportBtn=Export Metadata
-dataset.exportBtn.itemLabel.ddi=DDI
+dataset.exportBtn.itemLabel.ddi=DDI Codebook v2
dataset.exportBtn.itemLabel.dublinCore=Dublin Core
dataset.exportBtn.itemLabel.schemaDotOrg=Schema.org JSON-LD
dataset.exportBtn.itemLabel.datacite=DataCite
@@ -1934,7 +1934,7 @@ file.downloadBtn.format.all=All File Formats + Information
file.downloadBtn.format.tab=Tab-Delimited
file.downloadBtn.format.original={0} (Original File Format)
file.downloadBtn.format.rdata=RData
-file.downloadBtn.format.var=Variable Metadata
+file.downloadBtn.format.var=DDI Codebook v2
file.downloadBtn.format.citation=Data File Citation
file.download.filetype.unknown=Original File Format
file.more.information.link=Link to more file information for
From f2430c57b367a39ead1f73cbbbd5dfb40d3ad648 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Mon, 21 Oct 2024 10:59:50 -0400
Subject: [PATCH 055/270] Add API endpoint for comparing Dataset Versions
---
...-add-api-for-comparing-dataset-versions.md | 15 +++
doc/sphinx-guides/source/api/native-api.rst | 19 +++
.../harvard/iq/dataverse/DatasetVersion.java | 4 +
.../dataverse/DatasetVersionDifference.java | 123 +++++++++++++++---
.../harvard/iq/dataverse/api/Datasets.java | 17 +++
.../CuratePublishedDatasetVersionCommand.java | 2 +-
.../DatasetVersionDifferenceTest.java | 90 +++++++++++++
.../harvard/iq/dataverse/api/DatasetsIT.java | 80 ++++++++++++
.../edu/harvard/iq/dataverse/api/UtilIT.java | 20 ++-
9 files changed, 349 insertions(+), 21 deletions(-)
create mode 100644 doc/release-notes/10888-add-api-for-comparing-dataset-versions.md
create mode 100644 src/test/java/edu/harvard/iq/dataverse/DatasetVersionDifferenceTest.java
diff --git a/doc/release-notes/10888-add-api-for-comparing-dataset-versions.md b/doc/release-notes/10888-add-api-for-comparing-dataset-versions.md
new file mode 100644
index 00000000000..902bf1a4d02
--- /dev/null
+++ b/doc/release-notes/10888-add-api-for-comparing-dataset-versions.md
@@ -0,0 +1,15 @@
+The following API have been added:
+
+/api/datasets/{persistentId}/versions/{versionId0}/compare/{versionId1}
+
+This API lists the changes between 2 dataset versions. The Json response shows the changes per field within the Metadata block and the Terms Of Access. Also listed are the files that have been added or removed. Files that have been modified will also display the new file data plus the fields that have been modified.
+
+Old and New values are represented by "0" and "1" respectively.
+```json
+[
+ "ModifiedFieldName" = {
+ "0" : "old value",
+ "1" : "new value"
+ }
+]
+```
diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst
index f8b8620f121..e3aae7a122a 100644
--- a/doc/sphinx-guides/source/api/native-api.rst
+++ b/doc/sphinx-guides/source/api/native-api.rst
@@ -1642,6 +1642,25 @@ The fully expanded example above (without environment variables) looks like this
curl "https://demo.dataverse.org/api/datasets/24/versions/1.0/metadata/citation"
+Compare Versions of a Dataset
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Returns a list of fields that have changed between 2 Dataset versions within the Metadata and Terms of Access. Also includes the files that have been added or removed as well as files that have been modified.
+.. code-block:: bash
+
+ export SERVER_URL=https://demo.dataverse.org
+ export ID=24
+ export VERSION0=1.0
+ export VERSION1=:draft
+
+ curl "$SERVER_URL/api/datasets/$ID/versions/$VERSION0/compare/$VERSION1"
+
+The fully expanded example above (without environment variables) looks like this:
+
+.. code-block:: bash
+
+ curl "https://demo.dataverse.org/api/datasets/24/versions/:latest-published/compare/:draft"
+
Update Metadata For a Dataset
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetVersion.java b/src/main/java/edu/harvard/iq/dataverse/DatasetVersion.java
index 0433c425fd2..2a870c81767 100644
--- a/src/main/java/edu/harvard/iq/dataverse/DatasetVersion.java
+++ b/src/main/java/edu/harvard/iq/dataverse/DatasetVersion.java
@@ -102,6 +102,10 @@ public int compare(DatasetVersion o1, DatasetVersion o2) {
}
}
};
+ public static final JsonObjectBuilder compareVersions(DatasetVersion originalVersion, DatasetVersion newVersion) {
+ DatasetVersionDifference diff = new DatasetVersionDifference(newVersion, originalVersion);
+ return diff.compareVersionsAsJson();
+ }
// TODO: Determine the UI implications of various version states
//IMPORTANT: If you add a new value to this enum, you will also have to modify the
diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java
index eca0c84ae84..dc0c342cb03 100644
--- a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java
+++ b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java
@@ -5,19 +5,17 @@
import edu.harvard.iq.dataverse.datavariable.VariableMetadataUtil;
import edu.harvard.iq.dataverse.util.StringUtil;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
import java.util.logging.Logger;
+import edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder;
+import jakarta.json.Json;
+import jakarta.json.JsonArrayBuilder;
+import jakarta.json.JsonObjectBuilder;
import org.apache.commons.lang3.StringUtils;
import edu.harvard.iq.dataverse.util.BundleUtil;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
+
+import static edu.harvard.iq.dataverse.util.json.JsonPrinter.json;
/**
*
@@ -34,6 +32,7 @@ public final class DatasetVersionDifference {
private List addedFiles = new ArrayList<>();
private List removedFiles = new ArrayList<>();
private List changedFileMetadata = new ArrayList<>();
+ private Map>> changedFileMetadataDiff = new HashMap<>();
private List changedVariableMetadata = new ArrayList<>();
private List replacedFiles = new ArrayList<>();
private List changedTermsAccess = new ArrayList<>();
@@ -122,9 +121,12 @@ public DatasetVersionDifference(DatasetVersion newVersion, DatasetVersion origin
for (FileMetadata fmdn : newVersion.getFileMetadatas()) {
if (fmdo.getDataFile().equals(fmdn.getDataFile())) {
deleted = false;
- if (!compareFileMetadatas(fmdo, fmdn)) {
+ Map> fileMetadataDiff = compareFileMetadatas(fmdo, fmdn);
+ if (!fileMetadataDiff.isEmpty()) {
changedFileMetadata.add(fmdo);
changedFileMetadata.add(fmdn);
+ // TODO: find a better key for the map. needs to be something that doesn't change
+ changedFileMetadataDiff.put(fmdo, fileMetadataDiff);
}
if (!variableMetadataUtil.compareVariableMetadata(fmdo,fmdn) || !compareVarGroup(fmdo, fmdn)) {
changedVariableMetadata.add(fmdo);
@@ -551,25 +553,40 @@ private boolean compareVarGroup(FileMetadata fmdo, FileMetadata fmdn) {
}
}
- public static boolean compareFileMetadatas(FileMetadata fmdo, FileMetadata fmdn) {
-
+ public static Map> compareFileMetadatas(FileMetadata fmdo, FileMetadata fmdn) {
+ Map> fileMetadataChanged = new HashMap<>();
+ boolean equals = true;
if (!StringUtils.equals(StringUtil.nullToEmpty(fmdo.getDescription()), StringUtil.nullToEmpty(fmdn.getDescription()))) {
- return false;
+ equals = false;
+ fileMetadataChanged.put("Description",
+ List.of(StringUtil.nullToEmpty(fmdo.getDescription()), StringUtil.nullToEmpty(fmdn.getDescription())));
}
if (!StringUtils.equals(fmdo.getCategoriesByName().toString(), fmdn.getCategoriesByName().toString())) {
- return false;
+ equals = false;
+ fileMetadataChanged.put("Categories",
+ List.of(fmdo.getCategoriesByName().toString(), fmdn.getCategoriesByName().toString()));
}
if (!StringUtils.equals(fmdo.getLabel(), fmdn.getLabel())) {
- return false;
+ equals = false;
+ fileMetadataChanged.put("Label",
+ List.of(fmdo.getLabel(), fmdn.getLabel()));
}
if (!StringUtils.equals(fmdo.getProvFreeForm(), fmdn.getProvFreeForm())) {
- return false;
+ equals = false;
+ fileMetadataChanged.put("ProvFreeForm",
+ List.of(fmdo.getProvFreeForm(), fmdn.getProvFreeForm()));
}
-
- return fmdo.isRestricted() == fmdn.isRestricted();
+
+ if (fmdo.isRestricted() != fmdn.isRestricted()) {
+ equals = false;
+ fileMetadataChanged.put("isRestricted",
+ List.of(String.valueOf(fmdo.isRestricted()), String.valueOf(fmdn.isRestricted())));
+ }
+
+ return fileMetadataChanged;
}
private void compareValues(DatasetField originalField, DatasetField newField, boolean compound) {
@@ -1819,4 +1836,74 @@ private static boolean fieldsAreDifferent(DatasetField originalField, DatasetFie
}
return false;
}
+ public JsonObjectBuilder compareVersionsAsJson() {
+ JsonObjectBuilder job = new NullSafeJsonBuilder();
+
+ JsonObjectBuilder jobMetadata = new NullSafeJsonBuilder();
+ List> byBlock = getDetailDataByBlock();
+ for (List l : byBlock) {
+ for (DatasetField[] dsfArray : l) {
+ JsonObjectBuilder jb = new NullSafeJsonBuilder();
+ if (dsfArray[0].getDatasetFieldType().isPrimitive()) {
+ jb.add("0", dsfArray[0].getRawValue());
+ } else {
+ jb.add("0", dsfArray[0].getCompoundRawValue());
+ }
+ if (dsfArray[1].getDatasetFieldType().isPrimitive()) {
+ jb.add("1", dsfArray[1].getRawValue());
+ } else {
+ jb.add("1", dsfArray[1].getCompoundRawValue());
+ }
+ jobMetadata.add(dsfArray[0].getDatasetFieldType().getTitle(), jb);
+ }
+ }
+ if (!byBlock.isEmpty()) {
+ job.add("Metadata", jobMetadata);
+ }
+
+ // Format added, removed, and modified files
+ JsonObjectBuilder jobFiles = new NullSafeJsonBuilder();
+ if (!addedFiles.isEmpty()) {
+ JsonArrayBuilder jab = Json.createArrayBuilder();
+ addedFiles.forEach(f -> jab.add(json(f)));
+ jobFiles.add("added", jab);
+ }
+ if (!removedFiles.isEmpty()) {
+ JsonArrayBuilder jab = Json.createArrayBuilder();
+ removedFiles.forEach(f -> jab.add(json(f)));
+ jobFiles.add("removed", jab);
+ }
+ if (!changedFileMetadata.isEmpty()) {
+ JsonArrayBuilder jabDiffFiles = Json.createArrayBuilder();
+ changedFileMetadataDiff.entrySet().forEach(entry -> {
+ JsonObjectBuilder jobDiffFiles = new NullSafeJsonBuilder();
+ jobDiffFiles.add("fileMetadata", json(entry.getKey()));
+ entry.getValue().entrySet().forEach(e -> {
+ JsonObjectBuilder jobDiffField = new NullSafeJsonBuilder();
+ jobDiffField.add("0",e.getValue().get(0));
+ jobDiffField.add("1",e.getValue().get(1));
+ jobDiffFiles.add(e.getKey(), jobDiffField);
+ });
+ jabDiffFiles.add(jobDiffFiles);
+ });
+ jobFiles.add("modified", jabDiffFiles);
+ }
+ if (!addedFiles.isEmpty() || !removedFiles.isEmpty() || !changedFileMetadata.isEmpty()) {
+ job.add("Files", jobFiles);
+ }
+
+ // Format Terms Of Access changes
+ if (!changedTermsAccess.isEmpty()) {
+ JsonObjectBuilder jobTOA = new NullSafeJsonBuilder();
+ changedTermsAccess.forEach(toa -> {
+ JsonObjectBuilder jobValue = new NullSafeJsonBuilder();
+ jobValue.add("0",toa[1]);
+ jobValue.add("1",toa[2]);
+ jobTOA.add(toa[0], jobValue);
+ });
+ job.add("TermsOfAccess", jobTOA);
+ }
+
+ return job;
+ }
}
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
index 369a22fe8d7..d7b0c78e611 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
@@ -2992,6 +2992,23 @@ public Response cleanStorage(@Context ContainerRequestContext crc, @PathParam("i
}
+ @GET
+ @AuthRequired
+ @Path("{id}/versions/{versionId1}/compare/{versionId2}")
+ public Response getCompareVersions(@Context ContainerRequestContext crc, @PathParam("id") String id,
+ @PathParam("versionId1") String versionId1,
+ @PathParam("versionId2") String versionId2,
+ @Context UriInfo uriInfo, @Context HttpHeaders headers) {
+ try {
+ DataverseRequest req = createDataverseRequest(getRequestUser(crc));
+ DatasetVersion dsv1 = getDatasetVersionOrDie(req, versionId1, findDatasetOrDie(id), uriInfo, headers);
+ DatasetVersion dsv2 = getDatasetVersionOrDie(req, versionId2, findDatasetOrDie(id), uriInfo, headers);
+ return ok(DatasetVersion.compareVersions(dsv1, dsv2));
+ } catch (WrappedResponse wr) {
+ return wr.getResponse();
+ }
+ }
+
private static Set getDatasetFilenames(Dataset dataset) {
Set files = new HashSet<>();
for (DataFile dataFile: dataset.getFiles()) {
diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/CuratePublishedDatasetVersionCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/CuratePublishedDatasetVersionCommand.java
index e6e8279a314..e378e2e2ef7 100644
--- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/CuratePublishedDatasetVersionCommand.java
+++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/CuratePublishedDatasetVersionCommand.java
@@ -131,7 +131,7 @@ public Dataset execute(CommandContext ctxt) throws CommandException {
throw new IllegalCommandException(BundleUtil.getStringFromBundle("datasetversion.update.failure"), this);
} else {
- metadataUpdated = DatasetVersionDifference.compareFileMetadatas(publishedFmd, draftFmd);
+ metadataUpdated = !DatasetVersionDifference.compareFileMetadatas(publishedFmd, draftFmd).isEmpty();
publishedFmd.setLabel(draftFmd.getLabel());
publishedFmd.setDescription(draftFmd.getDescription());
publishedFmd.setCategories(draftFmd.getCategories());
diff --git a/src/test/java/edu/harvard/iq/dataverse/DatasetVersionDifferenceTest.java b/src/test/java/edu/harvard/iq/dataverse/DatasetVersionDifferenceTest.java
new file mode 100644
index 00000000000..9457cdbdb4d
--- /dev/null
+++ b/src/test/java/edu/harvard/iq/dataverse/DatasetVersionDifferenceTest.java
@@ -0,0 +1,90 @@
+package edu.harvard.iq.dataverse;
+
+import edu.harvard.iq.dataverse.pidproviders.doi.AbstractDOIProvider;
+import edu.harvard.iq.dataverse.util.json.JsonUtil;
+import io.restassured.path.json.JsonPath;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonObjectBuilder;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.assertj.core.util.DateUtil.now;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class DatasetVersionDifferenceTest {
+
+ @Test
+ public void testCompareVersionsAsJson() {
+
+ Dataverse dv = new Dataverse();
+ Dataset ds = new Dataset();
+ ds.setOwner(dv);
+ ds.setGlobalId(new GlobalId(AbstractDOIProvider.DOI_PROTOCOL,"10.5072","FK2/BYM3IW", "/", AbstractDOIProvider.DOI_RESOLVER_URL, null));
+
+ DatasetVersion dv1 = initDatasetVersion(0L, ds, DatasetVersion.VersionState.RELEASED);
+ DatasetVersion dv2 = initDatasetVersion(1L, ds, DatasetVersion.VersionState.DRAFT);
+ ds.setVersions(List.of(dv1, dv2));
+
+ TermsOfUseAndAccess toa = new TermsOfUseAndAccess();
+ toa.setDisclaimer("disclaimer");
+ dv2.setTermsOfUseAndAccess(toa);
+ dv2.getFileMetadatas().remove(1);
+ DatasetField dsf = new DatasetField();
+ dsf.setDatasetFieldType(new DatasetFieldType("Author", DatasetFieldType.FieldType.TEXT, true));
+ dsf.setSingleValue("TEST");
+ dv2.getDatasetFields().add(dsf);
+ dv2.getFileMetadatas().get(2).setRestricted(!dv2.getFileMetadatas().get(2).isRestricted());
+ DatasetVersionDifference dvd = new DatasetVersionDifference(dv2, dv1);
+
+ JsonObjectBuilder json = dvd.compareVersionsAsJson();
+ JsonObject obj = json.build();
+ System.out.println(JsonUtil.prettyPrint(obj));
+
+ JsonPath dataFile = JsonPath.from(JsonUtil.prettyPrint(obj));
+ assertTrue("TEST".equalsIgnoreCase(dataFile.getString("Metadata.Author.1")));
+ assertTrue("true".equalsIgnoreCase(dataFile.getString("Files.modified[0].isRestricted.1")));
+ assertTrue("disclaimer".equalsIgnoreCase(dataFile.getString("TermsOfAccess.Disclaimer.1")));
+ }
+ private DatasetVersion initDatasetVersion(Long id, Dataset ds, DatasetVersion.VersionState vs) {
+ DatasetVersion dv = new DatasetVersion();
+ dv.setDataset(ds);
+ dv.setVersion(1L);
+ dv.setVersionState(vs);
+ dv.setMinorVersionNumber(0L);
+ if (vs == DatasetVersion.VersionState.RELEASED) {
+ dv.setVersionNumber(1L);
+ dv.setVersion(1L);
+ dv.setReleaseTime(now());
+ }
+ dv.setId(id);
+ dv.setTermsOfUseAndAccess(new TermsOfUseAndAccess());
+ dv.setFileMetadatas(initFiles(dv));
+ return dv;
+ }
+ private List initFiles(DatasetVersion dsv) {
+ List fileMetadata = new ArrayList<>();
+ for (int i=0; i < 4; i++) {
+ FileMetadata fm = new FileMetadata();
+ fm.setDatasetVersion(dsv);
+ DataFile df = new DataFile();
+ DataTable dt = new DataTable();
+ dt.setOriginalFileName("filename"+i+".txt");
+ df.setId(Long.valueOf(i));
+ df.setDescription("Desc"+i);
+ df.setRestricted(false);
+ df.setFilesize(100 + i);
+ df.setChecksumType(DataFile.ChecksumType.MD5);
+ df.setChecksumValue("value"+i);
+ df.setDataTable(dt);
+ df.setOwner(dsv.getDataset());
+ fm.setDataFile(df);
+ fm.setLabel("Label"+i);
+ fileMetadata.add(fm);
+ df.setFileMetadatas(fileMetadata);
+
+ }
+ return fileMetadata;
+ }
+}
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
index 93f1024ae7a..af0f218bcf6 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
@@ -5168,4 +5168,84 @@ public void testGetCanDownloadAtLeastOneFile() {
Response getUserPermissionsOnDatasetInvalidIdResponse = UtilIT.getCanDownloadAtLeastOneFile("testInvalidId", DS_VERSION_LATEST, secondUserApiToken);
getUserPermissionsOnDatasetInvalidIdResponse.then().assertThat().statusCode(BAD_REQUEST.getStatusCode());
}
+
+ @Test
+ public void testCompareDatasetVersionsAPI() {
+
+ Response createUser = UtilIT.createRandomUser();
+ assertEquals(200, createUser.getStatusCode());
+ String username = UtilIT.getUsernameFromResponse(createUser);
+ String apiToken = UtilIT.getApiTokenFromResponse(createUser);
+ Response makeSuperUser = UtilIT.makeSuperUser(username);
+ assertEquals(200, makeSuperUser.getStatusCode());
+
+ Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken);
+ String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse);
+
+ Response createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken);
+ Integer datasetId = JsonPath.from(createDatasetResponse.body().asString()).getInt("data.id");
+
+ Response getDatasetJsonBeforePublishing = UtilIT.nativeGet(datasetId, apiToken);
+ String protocol = JsonPath.from(getDatasetJsonBeforePublishing.getBody().asString()).getString("data.protocol");
+ String authority = JsonPath.from(getDatasetJsonBeforePublishing.getBody().asString()).getString("data.authority");
+ String identifier = JsonPath.from(getDatasetJsonBeforePublishing.getBody().asString()).getString("data.identifier");
+ String datasetPersistentId = protocol + ":" + authority + "/" + identifier;
+
+ String pathToFile = "src/main/webapp/resources/images/dataverse-icon-1200.png";
+ Response uploadResponse = UtilIT.uploadFileViaNative(String.valueOf(datasetId), pathToFile, apiToken);
+ uploadResponse.then().assertThat()
+ .statusCode(OK.getStatusCode());
+ Integer modifyFileId = UtilIT.getDataFileIdFromResponse(uploadResponse);
+ pathToFile = "src/main/webapp/resources/images/dataverseproject_logo.jpg";
+ uploadResponse = UtilIT.uploadFileViaNative(String.valueOf(datasetId), pathToFile, apiToken);
+ uploadResponse.then().assertThat()
+ .statusCode(OK.getStatusCode());
+ Integer deleteFileId = UtilIT.getDataFileIdFromResponse(uploadResponse);
+
+ Response publishDataverse = UtilIT.publishDataverseViaSword(dataverseAlias, apiToken);
+ assertEquals(200, publishDataverse.getStatusCode());
+
+ Response publishDataset = UtilIT.publishDatasetViaNativeApi(datasetPersistentId, "major", apiToken);
+ assertEquals(200, publishDataset.getStatusCode());
+
+ // post publish update to create DRAFT version
+ String pathToJsonFilePostPub = "doc/sphinx-guides/source/_static/api/dataset-add-metadata-after-pub.json";
+ Response addDataToPublishedVersion = UtilIT.addDatasetMetadataViaNative(datasetPersistentId, pathToJsonFilePostPub, apiToken);
+ addDataToPublishedVersion.then().assertThat().statusCode(OK.getStatusCode());
+
+ // Test adding a file
+ pathToFile = "src/main/webapp/resources/images/dataverseproject.png";
+ uploadResponse = UtilIT.uploadFileViaNative(String.valueOf(datasetId), pathToFile, apiToken);
+ uploadResponse.prettyPrint();
+ uploadResponse.then().assertThat()
+ .statusCode(OK.getStatusCode());
+
+ // Test removing a file
+ uploadResponse = UtilIT.deleteFile(deleteFileId, apiToken);
+ uploadResponse.prettyPrint();
+ uploadResponse.then().assertThat()
+ .statusCode(NO_CONTENT.getStatusCode());
+
+ // Test modify by restricting the file
+ Response restrictResponse = UtilIT.restrictFile(modifyFileId.toString(), true, apiToken);
+ restrictResponse.prettyPrint();
+ restrictResponse.then().assertThat()
+ .statusCode(OK.getStatusCode());
+
+ // Also test a terms of access change
+ String jsonLDTerms = "{\"https://dataverse.org/schema/core#fileTermsOfAccess\":{\"https://dataverse.org/schema/core#dataAccessPlace\":\"Somewhere\"}}";
+ Response updateTerms = UtilIT.updateDatasetJsonLDMetadata(datasetId, apiToken, jsonLDTerms, true);
+ updateTerms.then().assertThat()
+ .statusCode(OK.getStatusCode());
+
+ Response compareResponse = UtilIT.compareDatasetVersions(datasetPersistentId, ":latest-published", ":draft", apiToken);
+ compareResponse.prettyPrint();
+ compareResponse.then().assertThat()
+ .body("data.Metadata.Author.1", CoreMatchers.containsString("Poe, Edgar Allen"))
+ .body("data.Files.added[0].label", CoreMatchers.equalTo("dataverseproject.png"))
+ .body("data.Files.removed[0].label", CoreMatchers.equalTo("dataverseproject_logo.jpg"))
+ .body("data.Files.modified[0].isRestricted.1", CoreMatchers.equalTo("true"))
+ .body("data.TermsOfAccess", CoreMatchers.notNullValue())
+ .statusCode(OK.getStatusCode());
+ }
}
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 70f49d81b35..26382884dd5 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java
@@ -323,7 +323,14 @@ static Integer getDatasetIdFromResponse(Response createDatasetResponse) {
logger.info("Id found in create dataset response: " + datasetId);
return datasetId;
}
-
+
+ static Integer getDataFileIdFromResponse(Response uploadDataFileResponse) {
+ JsonPath dataFile = JsonPath.from(uploadDataFileResponse.body().asString());
+ int dataFileId = dataFile.getInt("data.files[0].dataFile.id");
+ logger.info("Id found in upload DataFile response: " + dataFileId);
+ return dataFileId;
+ }
+
static Integer getSearchCountFromResponse(Response searchResponse) {
JsonPath createdDataset = JsonPath.from(searchResponse.body().asString());
int searchCount = createdDataset.getInt("data.total_count");
@@ -1570,7 +1577,16 @@ static Response getDatasetVersion(String persistentId, String versionNumber, Str
+ persistentId
+ (excludeFiles ? "&excludeFiles=true" : ""));
}
-
+ static Response compareDatasetVersions(String persistentId, String versionNumber1, String versionNumber2, String apiToken) {
+ return given()
+ .header(API_TOKEN_HTTP_HEADER, apiToken)
+ .get("/api/datasets/:persistentId/versions/"
+ + versionNumber1
+ + "/compare/"
+ + versionNumber2
+ + "?persistentId="
+ + persistentId);
+ }
static Response getDatasetWithOwners(String persistentId, String apiToken, boolean returnOwners) {
return given()
.header(API_TOKEN_HTTP_HEADER, apiToken)
From 6393cc868930925592faa9ff0861890d735aaf93 Mon Sep 17 00:00:00 2001
From: Philip Durbin
Date: Mon, 21 Oct 2024 16:11:38 -0400
Subject: [PATCH 056/270] update schemaspy #10931
---
doc/sphinx-guides/source/developers/making-releases.rst | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/doc/sphinx-guides/source/developers/making-releases.rst b/doc/sphinx-guides/source/developers/making-releases.rst
index 350f1fdcaf3..25297a23fca 100755
--- a/doc/sphinx-guides/source/developers/making-releases.rst
+++ b/doc/sphinx-guides/source/developers/making-releases.rst
@@ -280,6 +280,15 @@ Deploy Final Release on Demo
Above you already did the hard work of deploying a release candidate to https://demo.dataverse.org. It should be relatively straightforward to undeploy the release candidate and deploy the final release.
+Update SchemaSpy
+----------------
+
+We maintain SchemaSpy at URLs like https://guides.dataverse.org/en/6.3/schemaspy/index.html
+
+Get the attention of the core team and ask someone to update it for the new release.
+
+Consider updating `the thread `_ on the mailing list once the update is in place.
+
Add the Release to the Dataverse Roadmap
----------------------------------------
From c12932f0ca46582fcd5f2674db0bb9b895cb0b13 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Mon, 21 Oct 2024 16:42:07 -0400
Subject: [PATCH 057/270] updaing json format
---
...-add-api-for-comparing-dataset-versions.md | 18 ++--
.../dataverse/DatasetVersionDifference.java | 97 +++++++++++--------
.../DatasetVersionDifferenceTest.java | 10 +-
.../harvard/iq/dataverse/api/DatasetsIT.java | 16 ++-
4 files changed, 88 insertions(+), 53 deletions(-)
diff --git a/doc/release-notes/10888-add-api-for-comparing-dataset-versions.md b/doc/release-notes/10888-add-api-for-comparing-dataset-versions.md
index 902bf1a4d02..f9b3822d29d 100644
--- a/doc/release-notes/10888-add-api-for-comparing-dataset-versions.md
+++ b/doc/release-notes/10888-add-api-for-comparing-dataset-versions.md
@@ -4,12 +4,16 @@ The following API have been added:
This API lists the changes between 2 dataset versions. The Json response shows the changes per field within the Metadata block and the Terms Of Access. Also listed are the files that have been added or removed. Files that have been modified will also display the new file data plus the fields that have been modified.
-Old and New values are represented by "0" and "1" respectively.
+Example of Metadata Block field change:
```json
-[
- "ModifiedFieldName" = {
- "0" : "old value",
- "1" : "new value"
- }
-]
+{
+ "blockName": "Life Sciences Metadata",
+ "changed": [
+ {
+ "fieldName": "Design Type",
+ "oldValue": "",
+ "newValue": "Parallel Group Design; Nested Case Control Design"
+ }
+ ]
+}
```
diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java
index dc0c342cb03..66542b18c8e 100644
--- a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java
+++ b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java
@@ -5,6 +5,7 @@
import edu.harvard.iq.dataverse.datavariable.VariableMetadataUtil;
import edu.harvard.iq.dataverse.util.StringUtil;
+import java.text.SimpleDateFormat;
import java.util.*;
import java.util.logging.Logger;
@@ -15,8 +16,6 @@
import org.apache.commons.lang3.StringUtils;
import edu.harvard.iq.dataverse.util.BundleUtil;
-import static edu.harvard.iq.dataverse.util.json.JsonPrinter.json;
-
/**
*
* @author skraffmiller
@@ -1838,69 +1837,89 @@ private static boolean fieldsAreDifferent(DatasetField originalField, DatasetFie
}
public JsonObjectBuilder compareVersionsAsJson() {
JsonObjectBuilder job = new NullSafeJsonBuilder();
+ JsonObjectBuilder jobVersion = new NullSafeJsonBuilder();
+ jobVersion.add("versionNumber", originalVersion.getFriendlyVersionNumber());
+ jobVersion.add("createdDate", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(originalVersion.getCreateTime()));
+ job.add("oldVersion", jobVersion);
+ jobVersion = new NullSafeJsonBuilder();
+ jobVersion.add("versionNumber", newVersion.getFriendlyVersionNumber());
+ jobVersion.add("createdDate", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(newVersion.getCreateTime()));
+ job.add("newVersion", jobVersion);
- JsonObjectBuilder jobMetadata = new NullSafeJsonBuilder();
- List> byBlock = getDetailDataByBlock();
- for (List l : byBlock) {
- for (DatasetField[] dsfArray : l) {
- JsonObjectBuilder jb = new NullSafeJsonBuilder();
- if (dsfArray[0].getDatasetFieldType().isPrimitive()) {
- jb.add("0", dsfArray[0].getRawValue());
- } else {
- jb.add("0", dsfArray[0].getCompoundRawValue());
- }
- if (dsfArray[1].getDatasetFieldType().isPrimitive()) {
- jb.add("1", dsfArray[1].getRawValue());
- } else {
- jb.add("1", dsfArray[1].getCompoundRawValue());
+ if (!this.detailDataByBlock.isEmpty()) {
+ JsonArrayBuilder jabMetadata = Json.createArrayBuilder();
+ for (List blocks : detailDataByBlock) {
+ JsonObjectBuilder jobMetadata = new NullSafeJsonBuilder();
+ JsonArrayBuilder jab = Json.createArrayBuilder();
+ String blockDisplay = blocks.get(0)[0].getDatasetFieldType().getMetadataBlock().getDisplayName();
+ for (DatasetField[] dsfArray : blocks) {
+ JsonObjectBuilder jb = new NullSafeJsonBuilder();
+ jb.add("fieldName", dsfArray[0].getDatasetFieldType().getTitle());
+ if (dsfArray[0].getDatasetFieldType().isPrimitive()) {
+ jb.add("oldValue", dsfArray[0].getRawValue());
+ } else {
+ jb.add("oldValue", dsfArray[0].getCompoundRawValue());
+ }
+ if (dsfArray[1].getDatasetFieldType().isPrimitive()) {
+ jb.add("newValue", dsfArray[1].getRawValue());
+ } else {
+ jb.add("newValue", dsfArray[1].getCompoundRawValue());
+ }
+ jab.add(jb);
}
- jobMetadata.add(dsfArray[0].getDatasetFieldType().getTitle(), jb);
+ jobMetadata.add("blockName", blockDisplay);
+ jobMetadata.add("changed", jab);
+ jabMetadata.add(jobMetadata);
}
- }
- if (!byBlock.isEmpty()) {
- job.add("Metadata", jobMetadata);
+ job.add("metadataChanges", jabMetadata);
}
// Format added, removed, and modified files
- JsonObjectBuilder jobFiles = new NullSafeJsonBuilder();
+ JsonArrayBuilder jabDiffFiles = Json.createArrayBuilder();
if (!addedFiles.isEmpty()) {
JsonArrayBuilder jab = Json.createArrayBuilder();
- addedFiles.forEach(f -> jab.add(json(f)));
- jobFiles.add("added", jab);
+ addedFiles.forEach(f -> {
+ jab.add(new NullSafeJsonBuilder().add("fileName", f.getDataFile().getDisplayName()));
+ });
+ job.add("filesAdded", jab);
}
if (!removedFiles.isEmpty()) {
JsonArrayBuilder jab = Json.createArrayBuilder();
- removedFiles.forEach(f -> jab.add(json(f)));
- jobFiles.add("removed", jab);
+ removedFiles.forEach(f -> {
+ jab.add(new NullSafeJsonBuilder().add("fileName", f.getDataFile().getDisplayName()));
+ });
+ job.add("filesRemoved", jab);
}
if (!changedFileMetadata.isEmpty()) {
- JsonArrayBuilder jabDiffFiles = Json.createArrayBuilder();
changedFileMetadataDiff.entrySet().forEach(entry -> {
- JsonObjectBuilder jobDiffFiles = new NullSafeJsonBuilder();
- jobDiffFiles.add("fileMetadata", json(entry.getKey()));
+ JsonArrayBuilder jab = Json.createArrayBuilder();
+ JsonObjectBuilder jobChanges = new NullSafeJsonBuilder();
+ jobChanges.add("fileName", entry.getKey().getDataFile().getOriginalFileName());
entry.getValue().entrySet().forEach(e -> {
JsonObjectBuilder jobDiffField = new NullSafeJsonBuilder();
- jobDiffField.add("0",e.getValue().get(0));
- jobDiffField.add("1",e.getValue().get(1));
- jobDiffFiles.add(e.getKey(), jobDiffField);
+ jobDiffField.add("fieldName",e.getKey());
+ jobDiffField.add("oldValue",e.getValue().get(0));
+ jobDiffField.add("newValue",e.getValue().get(1));
+ jab.add(jobDiffField);
});
- jabDiffFiles.add(jobDiffFiles);
+ jobChanges.add("changes", jab);
+ jabDiffFiles.add(jobChanges);
});
- jobFiles.add("modified", jabDiffFiles);
- }
- if (!addedFiles.isEmpty() || !removedFiles.isEmpty() || !changedFileMetadata.isEmpty()) {
- job.add("Files", jobFiles);
+ job.add("fileChanges", jabDiffFiles);
}
// Format Terms Of Access changes
if (!changedTermsAccess.isEmpty()) {
JsonObjectBuilder jobTOA = new NullSafeJsonBuilder();
+ JsonArrayBuilder jab = Json.createArrayBuilder();
changedTermsAccess.forEach(toa -> {
JsonObjectBuilder jobValue = new NullSafeJsonBuilder();
- jobValue.add("0",toa[1]);
- jobValue.add("1",toa[2]);
- jobTOA.add(toa[0], jobValue);
+ jobValue.add("fieldName",toa[0]);
+ jobValue.add("oldValue",toa[1]);
+ jobValue.add("newValue",toa[2]);
+ jab.add(jobValue);
});
+ jobTOA.add("changed", jab);
job.add("TermsOfAccess", jobTOA);
}
diff --git a/src/test/java/edu/harvard/iq/dataverse/DatasetVersionDifferenceTest.java b/src/test/java/edu/harvard/iq/dataverse/DatasetVersionDifferenceTest.java
index 9457cdbdb4d..4b901f99afe 100644
--- a/src/test/java/edu/harvard/iq/dataverse/DatasetVersionDifferenceTest.java
+++ b/src/test/java/edu/harvard/iq/dataverse/DatasetVersionDifferenceTest.java
@@ -33,6 +33,9 @@ public void testCompareVersionsAsJson() {
dv2.getFileMetadatas().remove(1);
DatasetField dsf = new DatasetField();
dsf.setDatasetFieldType(new DatasetFieldType("Author", DatasetFieldType.FieldType.TEXT, true));
+ MetadataBlock mb = new MetadataBlock();
+ mb.setDisplayName("testMetadataBlock");
+ dsf.getDatasetFieldType().setMetadataBlock(mb);
dsf.setSingleValue("TEST");
dv2.getDatasetFields().add(dsf);
dv2.getFileMetadatas().get(2).setRestricted(!dv2.getFileMetadatas().get(2).isRestricted());
@@ -43,9 +46,9 @@ public void testCompareVersionsAsJson() {
System.out.println(JsonUtil.prettyPrint(obj));
JsonPath dataFile = JsonPath.from(JsonUtil.prettyPrint(obj));
- assertTrue("TEST".equalsIgnoreCase(dataFile.getString("Metadata.Author.1")));
- assertTrue("true".equalsIgnoreCase(dataFile.getString("Files.modified[0].isRestricted.1")));
- assertTrue("disclaimer".equalsIgnoreCase(dataFile.getString("TermsOfAccess.Disclaimer.1")));
+ assertTrue("TEST".equalsIgnoreCase(dataFile.getString("metadataChanges[0].changed[0].newValue")));
+ assertTrue("true".equalsIgnoreCase(dataFile.getString("fileChanges[0].changes[0].newValue")));
+ assertTrue("disclaimer".equalsIgnoreCase(dataFile.getString("TermsOfAccess.changed[0].newValue")));
}
private DatasetVersion initDatasetVersion(Long id, Dataset ds, DatasetVersion.VersionState vs) {
DatasetVersion dv = new DatasetVersion();
@@ -59,6 +62,7 @@ private DatasetVersion initDatasetVersion(Long id, Dataset ds, DatasetVersion.Ve
dv.setReleaseTime(now());
}
dv.setId(id);
+ dv.setCreateTime(now());
dv.setTermsOfUseAndAccess(new TermsOfUseAndAccess());
dv.setFileMetadatas(initFiles(dv));
return dv;
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
index af0f218bcf6..4b022b00cef 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
@@ -5241,10 +5241,18 @@ public void testCompareDatasetVersionsAPI() {
Response compareResponse = UtilIT.compareDatasetVersions(datasetPersistentId, ":latest-published", ":draft", apiToken);
compareResponse.prettyPrint();
compareResponse.then().assertThat()
- .body("data.Metadata.Author.1", CoreMatchers.containsString("Poe, Edgar Allen"))
- .body("data.Files.added[0].label", CoreMatchers.equalTo("dataverseproject.png"))
- .body("data.Files.removed[0].label", CoreMatchers.equalTo("dataverseproject_logo.jpg"))
- .body("data.Files.modified[0].isRestricted.1", CoreMatchers.equalTo("true"))
+ .body("data.oldVersion.versionNumber", CoreMatchers.equalTo("1.0"))
+ .body("data.newVersion.versionNumber", CoreMatchers.equalTo("DRAFT"))
+ .body("data.metadataChanges[0].blockName", CoreMatchers.equalTo("Citation Metadata"))
+ .body("data.metadataChanges[0].changed[0].fieldName", CoreMatchers.equalTo("Author"))
+ .body("data.metadataChanges[0].changed[0].oldValue", CoreMatchers.containsString("Finch, Fiona; (Birds Inc.)"))
+ .body("data.metadataChanges[1].blockName", CoreMatchers.equalTo("Life Sciences Metadata"))
+ .body("data.metadataChanges[1].changed[0].fieldName", CoreMatchers.equalTo("Design Type"))
+ .body("data.metadataChanges[1].changed[0].oldValue", CoreMatchers.containsString(""))
+ .body("data.metadataChanges[1].changed[0].newValue", CoreMatchers.containsString("Parallel Group Design; Nested Case Control Design"))
+ .body("data.filesAdded[0].fileName", CoreMatchers.equalTo("dataverseproject.png"))
+ .body("data.filesRemoved[0].fileName", CoreMatchers.equalTo("dataverseproject_logo.jpg"))
+ .body("data.fileChanges[0].changes[0].newValue", CoreMatchers.equalTo("true"))
.body("data.TermsOfAccess", CoreMatchers.notNullValue())
.statusCode(OK.getStatusCode());
}
From c8b9b38c5e356afca7f323b94d1302170aeb80da Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Tue, 22 Oct 2024 09:48:41 -0400
Subject: [PATCH 058/270] #8184 display url in popup upon creation
---
src/main/webapp/dataset.xhtml | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/src/main/webapp/dataset.xhtml b/src/main/webapp/dataset.xhtml
index ec23a9cdcfd..86bb6adb4b4 100644
--- a/src/main/webapp/dataset.xhtml
+++ b/src/main/webapp/dataset.xhtml
@@ -1198,6 +1198,20 @@
disabled="#{(!empty(DatasetPage.privateUrl) and DatasetPage.anonymizedPrivateUrl)}"
rendered="#{empty(DatasetPage.privateUrl) or (!empty(DatasetPage.privateUrl) and DatasetPage.anonymizedPrivateUrl) }"/>
+
#{bundle['copyClipboard']}
From 5ae208d3079903164610388bccae7cac7150bf51 Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Tue, 22 Oct 2024 09:59:50 -0400
Subject: [PATCH 059/270] #8184 add release notes
---
doc/release-notes/8184-rename-private-url.md | 7 +++++++
1 file changed, 7 insertions(+)
create mode 100644 doc/release-notes/8184-rename-private-url.md
diff --git a/doc/release-notes/8184-rename-private-url.md b/doc/release-notes/8184-rename-private-url.md
new file mode 100644
index 00000000000..600e8dd228c
--- /dev/null
+++ b/doc/release-notes/8184-rename-private-url.md
@@ -0,0 +1,7 @@
+###Private URL renamed Preview URL
+
+With this release the name of the URL that may be used by dataset administrators to share a draft version of a dataset has been changed from Private URL to Preview URL.
+
+Also, additional information about the creation of Preview URLs has been added to the popup accessed via edit menu of the Dataset Page.
+
+Any Private URLs created in previous versions of Dataverse will continue to work as will the api for creation and deletion of Private URLs.
From 05fd0f80ab7925259323eb623f2b5314411f4ae0 Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Tue, 22 Oct 2024 10:14:05 -0400
Subject: [PATCH 060/270] Update native-api.rst
---
doc/sphinx-guides/source/api/native-api.rst | 28 ++++++++++-----------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst
index f8b8620f121..5a596d5863b 100644
--- a/doc/sphinx-guides/source/api/native-api.rst
+++ b/doc/sphinx-guides/source/api/native-api.rst
@@ -1950,10 +1950,10 @@ The fully expanded example above (without environment variables) looks like this
curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X DELETE "https://demo.dataverse.org/api/datasets/2347/assignments/6"
-Create a Private URL for a Dataset
+Create a Preview URL for a Dataset
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Create a Private URL (must be able to manage dataset permissions):
+Create a Preview URL (must be able to manage dataset permissions):
.. code-block:: bash
@@ -1961,26 +1961,26 @@ Create a Private URL (must be able to manage dataset permissions):
export SERVER_URL=https://demo.dataverse.org
export ID=24
- curl -H "X-Dataverse-key: $API_TOKEN" -X POST "$SERVER_URL/api/datasets/$ID/privateUrl"
+ curl -H "X-Dataverse-key: $API_TOKEN" -X POST "$SERVER_URL/api/datasets/$ID/previewUrl"
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 POST "https://demo.dataverse.org/api/datasets/24/privateUrl"
+ curl -H "X-Dataverse-key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X POST "https://demo.dataverse.org/api/datasets/24/previewUrl"
If Anonymized Access has been enabled on a Dataverse installation (see the :ref:`:AnonymizedFieldTypeNames` setting), an optional 'anonymizedAccess' query parameter is allowed.
-Setting anonymizedAccess=true in your call will create a PrivateURL that only allows an anonymized view of the Dataset (see :ref:`privateurl`).
+Setting anonymizedAccess=true in your call will create a PreviewURL that only allows an anonymized view of the Dataset (see :ref:`previewurl`).
.. code-block:: bash
- curl -H "X-Dataverse-key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X POST "https://demo.dataverse.org/api/datasets/24/privateUrl?anonymizedAccess=true"
+ curl -H "X-Dataverse-key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X POST "https://demo.dataverse.org/api/datasets/24/previewUrl?anonymizedAccess=true"
-Get the Private URL for a Dataset
+Get the Preview URL for a Dataset
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Get a Private URL from a dataset (if available):
+Get a Preview URL from a dataset (if available):
.. code-block:: bash
@@ -1988,18 +1988,18 @@ Get a Private URL from a dataset (if available):
export SERVER_URL=https://demo.dataverse.org
export ID=24
- curl -H "X-Dataverse-key: $API_TOKEN" "$SERVER_URL/api/datasets/$ID/privateUrl"
+ curl -H "X-Dataverse-key: $API_TOKEN" "$SERVER_URL/api/datasets/$ID/previewUrl"
The fully expanded example above (without environment variables) looks like this:
.. code-block:: bash
- curl -H "X-Dataverse-key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/datasets/24/privateUrl"
+ curl -H "X-Dataverse-key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/datasets/24/previewUrl"
-Delete the Private URL from a Dataset
+Delete the Preview URL from a Dataset
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Delete a Private URL from a dataset (if it exists):
+Delete a Preview URL from a dataset (if it exists):
.. code-block:: bash
@@ -2007,13 +2007,13 @@ Delete a Private URL from a dataset (if it exists):
export SERVER_URL=https://demo.dataverse.org
export ID=24
- curl -H "X-Dataverse-key: $API_TOKEN" -X DELETE "$SERVER_URL/api/datasets/$ID/privateUrl"
+ curl -H "X-Dataverse-key: $API_TOKEN" -X DELETE "$SERVER_URL/api/datasets/$ID/previewUrl"
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 DELETE "https://demo.dataverse.org/api/datasets/24/privateUrl"
+ curl -H "X-Dataverse-key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X DELETE "https://demo.dataverse.org/api/datasets/24/previewUrl"
.. _add-file-api:
From 9a1a0d82bfc6a99a924ea9accdbfca97a0a76471 Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Tue, 22 Oct 2024 10:25:58 -0400
Subject: [PATCH 061/270] Update native-api.rst
---
doc/sphinx-guides/source/api/native-api.rst | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst
index 5a596d5863b..c90f34db568 100644
--- a/doc/sphinx-guides/source/api/native-api.rst
+++ b/doc/sphinx-guides/source/api/native-api.rst
@@ -2872,15 +2872,15 @@ Signposting is not supported for draft dataset versions.
curl -H "Accept:application/json" "$SERVER_URL/api/datasets/:persistentId/versions/$VERSION/linkset?persistentId=$PERSISTENT_IDENTIFIER"
-Get Dataset By Private URL Token
+Get Dataset By Preview URL Token
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: bash
export SERVER_URL=https://demo.dataverse.org
- export PRIVATE_URL_TOKEN=a56444bc-7697-4711-8964-e0577f055fd2
+ export PREVIEW_URL_TOKEN=a56444bc-7697-4711-8964-e0577f055fd2
- curl "$SERVER_URL/api/datasets/privateUrlDatasetVersion/$PRIVATE_URL_TOKEN"
+ curl "$SERVER_URL/api/datasets/privateUrlDatasetVersion/$PREVIEW_URL_TOKEN"
If you want to include the Dataverse collections that this dataset is part of, you must set ``returnOwners`` query parameter to ``true``.
@@ -2888,7 +2888,7 @@ Usage example:
.. code-block:: bash
- curl "https://demo.dataverse.org/api/datasets/privateUrlDatasetVersion/a56444bc-7697-4711-8964-e0577f055fd2?returnOwners=true"
+ curl "https://demo.dataverse.org/api/datasets/previewUrlDatasetVersion/a56444bc-7697-4711-8964-e0577f055fd2?returnOwners=true"
.. _get-citation:
@@ -2914,15 +2914,15 @@ Usage example:
curl -H "Accept:application/json" "$SERVER_URL/api/datasets/:persistentId/versions/$VERSION/{version}/citation?persistentId=$PERSISTENT_IDENTIFIER&includeDeaccessioned=true"
-Get Citation by Private URL Token
+Get Citation by Preview URL Token
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: bash
export SERVER_URL=https://demo.dataverse.org
- export PRIVATE_URL_TOKEN=a56444bc-7697-4711-8964-e0577f055fd2
+ export PREVIEW_URL_TOKEN=a56444bc-7697-4711-8964-e0577f055fd2
- curl "$SERVER_URL/api/datasets/privateUrlDatasetVersion/$PRIVATE_URL_TOKEN/citation"
+ curl "$SERVER_URL/api/datasets/previewUrlDatasetVersion/$PREVIEW_URL_TOKEN/citation"
.. _get-dataset-summary-field-names:
From c1acf337dfd85dddfe0aeb082640a50b92794b25 Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Tue, 22 Oct 2024 10:39:02 -0400
Subject: [PATCH 062/270] #8184 add endpoints for get by token
---
.../harvard/iq/dataverse/api/Datasets.java | 39 +++++++++++++++++++
1 file changed, 39 insertions(+)
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
index 3eee734877b..52610e10323 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
@@ -4876,6 +4876,33 @@ public Response getPrivateUrlDatasetVersion(@PathParam("privateUrlToken") String
}
return ok(responseJson);
}
+
+ @GET
+ @Path("previewUrlDatasetVersion/{previewUrlToken}")
+ public Response getPreviewUrlDatasetVersion(@PathParam("previewUrlToken") String previewUrlToken, @QueryParam("returnOwners") boolean returnOwners) {
+ PrivateUrlUser privateUrlUser = privateUrlService.getPrivateUrlUserFromToken(previewUrlToken);
+ if (privateUrlUser == null) {
+ return notFound("Private URL user not found");
+ }
+ boolean isAnonymizedAccess = privateUrlUser.hasAnonymizedAccess();
+ String anonymizedFieldTypeNames = settingsSvc.getValueForKey(SettingsServiceBean.Key.AnonymizedFieldTypeNames);
+ if(isAnonymizedAccess && anonymizedFieldTypeNames == null) {
+ throw new NotAcceptableException("Anonymized Access not enabled");
+ }
+ DatasetVersion dsv = privateUrlService.getDraftDatasetVersionFromToken(previewUrlToken);
+ if (dsv == null || dsv.getId() == null) {
+ return notFound("Dataset version not found");
+ }
+ JsonObjectBuilder responseJson;
+ if (isAnonymizedAccess) {
+ List anonymizedFieldTypeNamesList = new ArrayList<>(Arrays.asList(anonymizedFieldTypeNames.split(",\\s")));
+ responseJson = json(dsv, anonymizedFieldTypeNamesList, true, returnOwners);
+ } else {
+ responseJson = json(dsv, null, true, returnOwners);
+ }
+ return ok(responseJson);
+ }
+
@GET
@Path("privateUrlDatasetVersion/{privateUrlToken}/citation")
@@ -4888,6 +4915,18 @@ public Response getPrivateUrlDatasetVersionCitation(@PathParam("privateUrlToken"
return (dsv == null || dsv.getId() == null) ? notFound("Dataset version not found")
: ok(dsv.getCitation(true, privateUrlUser.hasAnonymizedAccess()));
}
+
+ @GET
+ @Path("previewUrlDatasetVersion/{previewUrlToken}/citation")
+ public Response getPreviewUrlDatasetVersionCitation(@PathParam("previewUrlToken") String previewUrlToken) {
+ PrivateUrlUser privateUrlUser = privateUrlService.getPrivateUrlUserFromToken(previewUrlToken);
+ if (privateUrlUser == null) {
+ return notFound("Private URL user not found");
+ }
+ DatasetVersion dsv = privateUrlService.getDraftDatasetVersionFromToken(previewUrlToken);
+ return (dsv == null || dsv.getId() == null) ? notFound("Dataset version not found")
+ : ok(dsv.getCitation(true, privateUrlUser.hasAnonymizedAccess()));
+ }
@GET
@AuthRequired
From 842f04b7167cb9f1ee16226b40859f9955dd82b6 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Tue, 22 Oct 2024 10:53:58 -0400
Subject: [PATCH 063/270] fixes for json output
---
.../harvard/iq/dataverse/DatasetVersionDifference.java | 8 ++++----
.../java/edu/harvard/iq/dataverse/api/DatasetsIT.java | 3 ++-
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java
index 66542b18c8e..7e5750959ea 100644
--- a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java
+++ b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java
@@ -1893,8 +1893,8 @@ public JsonObjectBuilder compareVersionsAsJson() {
if (!changedFileMetadata.isEmpty()) {
changedFileMetadataDiff.entrySet().forEach(entry -> {
JsonArrayBuilder jab = Json.createArrayBuilder();
- JsonObjectBuilder jobChanges = new NullSafeJsonBuilder();
- jobChanges.add("fileName", entry.getKey().getDataFile().getOriginalFileName());
+ JsonObjectBuilder jobChanged = new NullSafeJsonBuilder();
+ jobChanged.add("fileName", entry.getKey().getDataFile().getDisplayName());
entry.getValue().entrySet().forEach(e -> {
JsonObjectBuilder jobDiffField = new NullSafeJsonBuilder();
jobDiffField.add("fieldName",e.getKey());
@@ -1902,8 +1902,8 @@ public JsonObjectBuilder compareVersionsAsJson() {
jobDiffField.add("newValue",e.getValue().get(1));
jab.add(jobDiffField);
});
- jobChanges.add("changes", jab);
- jabDiffFiles.add(jobChanges);
+ jobChanged.add("changed", jab);
+ jabDiffFiles.add(jobChanged);
});
job.add("fileChanges", jabDiffFiles);
}
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
index 4b022b00cef..4d515a64cf5 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
@@ -5252,7 +5252,8 @@ public void testCompareDatasetVersionsAPI() {
.body("data.metadataChanges[1].changed[0].newValue", CoreMatchers.containsString("Parallel Group Design; Nested Case Control Design"))
.body("data.filesAdded[0].fileName", CoreMatchers.equalTo("dataverseproject.png"))
.body("data.filesRemoved[0].fileName", CoreMatchers.equalTo("dataverseproject_logo.jpg"))
- .body("data.fileChanges[0].changes[0].newValue", CoreMatchers.equalTo("true"))
+ .body("data.fileChanges[0].fileName", CoreMatchers.equalTo("dataverse-icon-1200.png"))
+ .body("data.fileChanges[0].changed[0].newValue", CoreMatchers.equalTo("true"))
.body("data.TermsOfAccess", CoreMatchers.notNullValue())
.statusCode(OK.getStatusCode());
}
From c5adf921202991fafc222325d3dffa1b803525ee Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Tue, 22 Oct 2024 17:18:52 -0400
Subject: [PATCH 064/270] fixes for json output
---
.../dataverse/DatasetVersionDifference.java | 37 +++++++++-
.../DatasetVersionDifferenceTest.java | 73 +++++++++++++------
.../harvard/iq/dataverse/api/DatasetsIT.java | 37 ++++++++--
3 files changed, 114 insertions(+), 33 deletions(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java
index 7e5750959ea..5805359feb0 100644
--- a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java
+++ b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java
@@ -1839,7 +1839,7 @@ public JsonObjectBuilder compareVersionsAsJson() {
JsonObjectBuilder job = new NullSafeJsonBuilder();
JsonObjectBuilder jobVersion = new NullSafeJsonBuilder();
jobVersion.add("versionNumber", originalVersion.getFriendlyVersionNumber());
- jobVersion.add("createdDate", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(originalVersion.getCreateTime()));
+ jobVersion.add("lastUpdatedDate", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(originalVersion.getLastUpdateTime()));
job.add("oldVersion", jobVersion);
jobVersion = new NullSafeJsonBuilder();
jobVersion.add("versionNumber", newVersion.getFriendlyVersionNumber());
@@ -1879,22 +1879,36 @@ public JsonObjectBuilder compareVersionsAsJson() {
if (!addedFiles.isEmpty()) {
JsonArrayBuilder jab = Json.createArrayBuilder();
addedFiles.forEach(f -> {
- jab.add(new NullSafeJsonBuilder().add("fileName", f.getDataFile().getDisplayName()));
+ jab.add(filesDiffJson(f));
});
job.add("filesAdded", jab);
}
if (!removedFiles.isEmpty()) {
JsonArrayBuilder jab = Json.createArrayBuilder();
removedFiles.forEach(f -> {
- jab.add(new NullSafeJsonBuilder().add("fileName", f.getDataFile().getDisplayName()));
+ jab.add(filesDiffJson(f));
});
job.add("filesRemoved", jab);
}
+ if (!replacedFiles.isEmpty()) {
+ JsonArrayBuilder jabReplaced = Json.createArrayBuilder();
+ replacedFiles.forEach(fm -> {
+ if (fm.length == 2) {
+ JsonObjectBuilder jobReplaced = new NullSafeJsonBuilder();
+ jobReplaced.add("oldFile", filesDiffJson(fm[0]));
+ jobReplaced.add("newFile", filesDiffJson(fm[1]));
+ jabReplaced.add(jobReplaced);
+ }
+ });
+ job.add("filesReplaced", jabReplaced);
+ }
if (!changedFileMetadata.isEmpty()) {
changedFileMetadataDiff.entrySet().forEach(entry -> {
JsonArrayBuilder jab = Json.createArrayBuilder();
JsonObjectBuilder jobChanged = new NullSafeJsonBuilder();
jobChanged.add("fileName", entry.getKey().getDataFile().getDisplayName());
+ jobChanged.add(entry.getKey().getDataFile().getChecksumType().name(), entry.getKey().getDataFile().getChecksumValue());
+ jobChanged.add("fileId", entry.getKey().getDataFile().getId());
entry.getValue().entrySet().forEach(e -> {
JsonObjectBuilder jobDiffField = new NullSafeJsonBuilder();
jobDiffField.add("fieldName",e.getKey());
@@ -1925,4 +1939,21 @@ public JsonObjectBuilder compareVersionsAsJson() {
return job;
}
+ private JsonObjectBuilder filesDiffJson(FileMetadata fileMetadata) {
+ NullSafeJsonBuilder job = new NullSafeJsonBuilder();
+ DataFile df = fileMetadata.getDataFile();
+ List tags = df.getTags();
+ job.add("fileName", df.getDisplayName())
+ .add(df.getChecksumType().name(), df.getChecksumValue())
+ .add("type",df.getContentType())
+ .add("fileId", df.getId())
+ .add("description", df.getDescription())
+ .add("isRestricted", df.isRestricted());
+ if (df.getTags() != null && !df.getTags().isEmpty()) {
+ JsonArrayBuilder jabTags = Json.createArrayBuilder();
+ df.getTags().forEach(t -> jabTags.add(t.getTypeLabel()));
+ job.add("tags", jabTags);
+ }
+ return job;
+ }
}
diff --git a/src/test/java/edu/harvard/iq/dataverse/DatasetVersionDifferenceTest.java b/src/test/java/edu/harvard/iq/dataverse/DatasetVersionDifferenceTest.java
index 4b901f99afe..f65f510090e 100644
--- a/src/test/java/edu/harvard/iq/dataverse/DatasetVersionDifferenceTest.java
+++ b/src/test/java/edu/harvard/iq/dataverse/DatasetVersionDifferenceTest.java
@@ -15,6 +15,7 @@
public class DatasetVersionDifferenceTest {
+ static Long fileId = Long.valueOf(0);
@Test
public void testCompareVersionsAsJson() {
@@ -30,7 +31,6 @@ public void testCompareVersionsAsJson() {
TermsOfUseAndAccess toa = new TermsOfUseAndAccess();
toa.setDisclaimer("disclaimer");
dv2.setTermsOfUseAndAccess(toa);
- dv2.getFileMetadatas().remove(1);
DatasetField dsf = new DatasetField();
dsf.setDatasetFieldType(new DatasetFieldType("Author", DatasetFieldType.FieldType.TEXT, true));
MetadataBlock mb = new MetadataBlock();
@@ -38,7 +38,16 @@ public void testCompareVersionsAsJson() {
dsf.getDatasetFieldType().setMetadataBlock(mb);
dsf.setSingleValue("TEST");
dv2.getDatasetFields().add(dsf);
- dv2.getFileMetadatas().get(2).setRestricted(!dv2.getFileMetadatas().get(2).isRestricted());
+ // modify file at index 0
+ dv2.getFileMetadatas().get(0).setRestricted(!dv2.getFileMetadatas().get(2).isRestricted());
+
+ FileMetadata addedFile = initFile(dv2); // add a new file
+ FileMetadata removedFile = dv2.getFileMetadatas().get(1); // remove the second file
+ dv2.getFileMetadatas().remove(1);
+ FileMetadata replacedFile = dv2.getFileMetadatas().get(1); // the third file is now at index 1 since the second file was removed
+ FileMetadata replacementFile = initFile(dv2, replacedFile.getDataFile().getId()); // replace the third file with a new file
+ dv2.getFileMetadatas().remove(1);
+
DatasetVersionDifference dvd = new DatasetVersionDifference(dv2, dv1);
JsonObjectBuilder json = dvd.compareVersionsAsJson();
@@ -47,7 +56,11 @@ public void testCompareVersionsAsJson() {
JsonPath dataFile = JsonPath.from(JsonUtil.prettyPrint(obj));
assertTrue("TEST".equalsIgnoreCase(dataFile.getString("metadataChanges[0].changed[0].newValue")));
- assertTrue("true".equalsIgnoreCase(dataFile.getString("fileChanges[0].changes[0].newValue")));
+ assertTrue(addedFile.getLabel().equalsIgnoreCase(dataFile.getString("filesAdded[0].fileName")));
+ assertTrue(removedFile.getLabel().equalsIgnoreCase(dataFile.getString("filesRemoved[0].fileName")));
+ assertTrue(replacedFile.getLabel().equalsIgnoreCase(dataFile.getString("filesReplaced[0].oldFile.fileName")));
+ assertTrue(replacementFile.getLabel().equalsIgnoreCase(dataFile.getString("filesReplaced[0].newFile.fileName")));
+ assertTrue("true".equalsIgnoreCase(dataFile.getString("fileChanges[0].changed[0].newValue")));
assertTrue("disclaimer".equalsIgnoreCase(dataFile.getString("TermsOfAccess.changed[0].newValue")));
}
private DatasetVersion initDatasetVersion(Long id, Dataset ds, DatasetVersion.VersionState vs) {
@@ -63,32 +76,44 @@ private DatasetVersion initDatasetVersion(Long id, Dataset ds, DatasetVersion.Ve
}
dv.setId(id);
dv.setCreateTime(now());
+ dv.setLastUpdateTime(now());
dv.setTermsOfUseAndAccess(new TermsOfUseAndAccess());
dv.setFileMetadatas(initFiles(dv));
return dv;
}
private List initFiles(DatasetVersion dsv) {
- List fileMetadata = new ArrayList<>();
- for (int i=0; i < 4; i++) {
- FileMetadata fm = new FileMetadata();
- fm.setDatasetVersion(dsv);
- DataFile df = new DataFile();
- DataTable dt = new DataTable();
- dt.setOriginalFileName("filename"+i+".txt");
- df.setId(Long.valueOf(i));
- df.setDescription("Desc"+i);
- df.setRestricted(false);
- df.setFilesize(100 + i);
- df.setChecksumType(DataFile.ChecksumType.MD5);
- df.setChecksumValue("value"+i);
- df.setDataTable(dt);
- df.setOwner(dsv.getDataset());
- fm.setDataFile(df);
- fm.setLabel("Label"+i);
- fileMetadata.add(fm);
- df.setFileMetadatas(fileMetadata);
-
+ List fileMetadatas = new ArrayList<>();
+ fileId = 0L;
+ for (int i=0; i < 10; i++) {
+ FileMetadata fm = initFile(dsv);
+ fileMetadatas.add(fm);
}
- return fileMetadata;
+ return fileMetadatas;
+ }
+ private FileMetadata initFile(DatasetVersion dsv) {
+ return initFile(dsv, null);
+ }
+ private FileMetadata initFile(DatasetVersion dsv, Long prevId) {
+ Long id = fileId++;
+ FileMetadata fm = new FileMetadata();
+ DataFile df = new DataFile();
+ fm.setDatasetVersion(dsv);
+ DataTable dt = new DataTable();
+ dt.setOriginalFileName("filename"+id+".txt");
+ df.setId(id);
+ df.setDescription("Desc"+id);
+ df.setRestricted(false);
+ df.setFilesize(100 + id);
+ df.setChecksumType(DataFile.ChecksumType.MD5);
+ df.setChecksumValue("value"+id);
+ df.setDataTable(dt);
+ df.setOwner(dsv.getDataset());
+ df.getFileMetadatas().add(fm);
+ df.setPreviousDataFileId(prevId);
+ fm.setId(id);
+ fm.setDataFile(df);
+ fm.setLabel("Label"+id);
+ dsv.getFileMetadatas().add(fm);
+ return fm;
}
}
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
index 4d515a64cf5..bf292723ba5 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
@@ -5170,7 +5170,7 @@ public void testGetCanDownloadAtLeastOneFile() {
}
@Test
- public void testCompareDatasetVersionsAPI() {
+ public void testCompareDatasetVersionsAPI() throws InterruptedException {
Response createUser = UtilIT.createRandomUser();
assertEquals(200, createUser.getStatusCode());
@@ -5202,6 +5202,12 @@ public void testCompareDatasetVersionsAPI() {
.statusCode(OK.getStatusCode());
Integer deleteFileId = UtilIT.getDataFileIdFromResponse(uploadResponse);
+ pathToFile = "src/main/webapp/resources/images/fav/favicon-16x16.png";
+ uploadResponse = UtilIT.uploadFileViaNative(String.valueOf(datasetId), pathToFile, apiToken);
+ uploadResponse.then().assertThat()
+ .statusCode(OK.getStatusCode());
+ Integer replaceFileId = UtilIT.getDataFileIdFromResponse(uploadResponse);
+
Response publishDataverse = UtilIT.publishDataverseViaSword(dataverseAlias, apiToken);
assertEquals(200, publishDataverse.getStatusCode());
@@ -5214,10 +5220,20 @@ public void testCompareDatasetVersionsAPI() {
addDataToPublishedVersion.then().assertThat().statusCode(OK.getStatusCode());
// Test adding a file
- pathToFile = "src/main/webapp/resources/images/dataverseproject.png";
- uploadResponse = UtilIT.uploadFileViaNative(String.valueOf(datasetId), pathToFile, apiToken);
- uploadResponse.prettyPrint();
- uploadResponse.then().assertThat()
+ pathToFile = "src/test/resources/tab/test.tab";
+ Response uploadTabularFileResponse = UtilIT.uploadFileViaNative(Integer.toString(datasetId), pathToFile, Json.createObjectBuilder().build(), apiToken);
+ uploadTabularFileResponse.prettyPrint();
+ uploadTabularFileResponse.then().assertThat()
+ .statusCode(OK.getStatusCode());
+ Integer addedFileId = UtilIT.getDataFileIdFromResponse(uploadTabularFileResponse);
+
+ // Ensure tabular file is ingested
+ sleep(2000);
+
+ String tabularTagName = "Survey";
+ Response setFileTabularTagsResponse = UtilIT.setFileTabularTags(String.valueOf(addedFileId), apiToken, List.of(tabularTagName));
+ setFileTabularTagsResponse.prettyPrint();
+ setFileTabularTagsResponse.then().assertThat()
.statusCode(OK.getStatusCode());
// Test removing a file
@@ -5226,6 +5242,12 @@ public void testCompareDatasetVersionsAPI() {
uploadResponse.then().assertThat()
.statusCode(NO_CONTENT.getStatusCode());
+ // Test Replacing a file
+ Response replaceResponse = UtilIT.replaceFile(String.valueOf(replaceFileId), "src/main/webapp/resources/images/fav/favicon-32x32.png", apiToken);
+ replaceResponse.prettyPrint();
+ replaceResponse.then().assertThat()
+ .statusCode(OK.getStatusCode());
+
// Test modify by restricting the file
Response restrictResponse = UtilIT.restrictFile(modifyFileId.toString(), true, apiToken);
restrictResponse.prettyPrint();
@@ -5250,10 +5272,13 @@ public void testCompareDatasetVersionsAPI() {
.body("data.metadataChanges[1].changed[0].fieldName", CoreMatchers.equalTo("Design Type"))
.body("data.metadataChanges[1].changed[0].oldValue", CoreMatchers.containsString(""))
.body("data.metadataChanges[1].changed[0].newValue", CoreMatchers.containsString("Parallel Group Design; Nested Case Control Design"))
- .body("data.filesAdded[0].fileName", CoreMatchers.equalTo("dataverseproject.png"))
+ .body("data.filesAdded[0].fileName", CoreMatchers.equalTo("test.tab"))
+ .body("data.filesAdded[0].tags[0]", CoreMatchers.equalTo("Survey"))
.body("data.filesRemoved[0].fileName", CoreMatchers.equalTo("dataverseproject_logo.jpg"))
.body("data.fileChanges[0].fileName", CoreMatchers.equalTo("dataverse-icon-1200.png"))
.body("data.fileChanges[0].changed[0].newValue", CoreMatchers.equalTo("true"))
+ .body("data.filesReplaced[0].oldFile.fileName", CoreMatchers.equalTo("favicon-16x16.png"))
+ .body("data.filesReplaced[0].newFile.fileName", CoreMatchers.equalTo("favicon-32x32.png"))
.body("data.TermsOfAccess", CoreMatchers.notNullValue())
.statusCode(OK.getStatusCode());
}
From d957f60d5969c42697856adda927be160f0991c8 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Thu, 24 Oct 2024 11:28:45 -0400
Subject: [PATCH 065/270] fixes for json output
---
.../dataverse/DatasetVersionDifference.java | 11 +++++++---
.../iq/dataverse/util/json/JsonParser.java | 1 -
.../DatasetVersionDifferenceTest.java | 2 ++
.../harvard/iq/dataverse/api/DatasetsIT.java | 22 ++++++++++++++-----
4 files changed, 26 insertions(+), 10 deletions(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java
index 5805359feb0..5760aa2c5b5 100644
--- a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java
+++ b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java
@@ -1843,7 +1843,7 @@ public JsonObjectBuilder compareVersionsAsJson() {
job.add("oldVersion", jobVersion);
jobVersion = new NullSafeJsonBuilder();
jobVersion.add("versionNumber", newVersion.getFriendlyVersionNumber());
- jobVersion.add("createdDate", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(newVersion.getCreateTime()));
+ jobVersion.add("lastUpdatedDate", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(newVersion.getLastUpdateTime()));
job.add("newVersion", jobVersion);
if (!this.detailDataByBlock.isEmpty()) {
@@ -1942,13 +1942,18 @@ public JsonObjectBuilder compareVersionsAsJson() {
private JsonObjectBuilder filesDiffJson(FileMetadata fileMetadata) {
NullSafeJsonBuilder job = new NullSafeJsonBuilder();
DataFile df = fileMetadata.getDataFile();
- List tags = df.getTags();
job.add("fileName", df.getDisplayName())
+ .add("filePath", fileMetadata.getDirectoryLabel())
.add(df.getChecksumType().name(), df.getChecksumValue())
.add("type",df.getContentType())
.add("fileId", df.getId())
- .add("description", df.getDescription())
+ .add("description", fileMetadata.getDescription())
.add("isRestricted", df.isRestricted());
+ if (fileMetadata.getCategories() != null && !fileMetadata.getCategories().isEmpty()) {
+ JsonArrayBuilder jabCategories = Json.createArrayBuilder();
+ fileMetadata.getCategories().forEach(c -> jabCategories.add(c.getName()));
+ job.add("categories", jabCategories);
+ }
if (df.getTags() != null && !df.getTags().isEmpty()) {
JsonArrayBuilder jabTags = Json.createArrayBuilder();
df.getTags().forEach(t -> jabTags.add(t.getTypeLabel()));
diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java
index 2f01c9bc2f2..65dd2986a86 100644
--- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java
+++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java
@@ -965,7 +965,6 @@ public void parseControlledVocabularyValue(DatasetField dsf, DatasetFieldType cv
if (cvv == null) {
if (allowHarvestingMissingCVV) {
// we need to process this as a primitive value
- logger.warning(">>>> Value '" + strValue + "' does not exist in type '" + cvvType.getName() + "'. Processing as primitive per setting override.");
parsePrimitiveValue(dsf, cvvType , json);
return;
} else {
diff --git a/src/test/java/edu/harvard/iq/dataverse/DatasetVersionDifferenceTest.java b/src/test/java/edu/harvard/iq/dataverse/DatasetVersionDifferenceTest.java
index f65f510090e..138ef709d18 100644
--- a/src/test/java/edu/harvard/iq/dataverse/DatasetVersionDifferenceTest.java
+++ b/src/test/java/edu/harvard/iq/dataverse/DatasetVersionDifferenceTest.java
@@ -113,6 +113,8 @@ private FileMetadata initFile(DatasetVersion dsv, Long prevId) {
fm.setId(id);
fm.setDataFile(df);
fm.setLabel("Label"+id);
+ fm.setDirectoryLabel("/myFilePath/");
+ fm.setDescription("Desc"+id);
dsv.getFileMetadatas().add(fm);
return fm;
}
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
index bf292723ba5..61977f6446c 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
@@ -5190,20 +5190,28 @@ public void testCompareDatasetVersionsAPI() throws InterruptedException {
String authority = JsonPath.from(getDatasetJsonBeforePublishing.getBody().asString()).getString("data.authority");
String identifier = JsonPath.from(getDatasetJsonBeforePublishing.getBody().asString()).getString("data.identifier");
String datasetPersistentId = protocol + ":" + authority + "/" + identifier;
-
+ // used for all added files
+ JsonObjectBuilder json = Json.createObjectBuilder()
+ .add("description", "my description")
+ .add("directoryLabel", "/data/subdir1/")
+ .add("categories", Json.createArrayBuilder()
+ .add("Data")
+ );
+ JsonObject jsonObj = json.build();
String pathToFile = "src/main/webapp/resources/images/dataverse-icon-1200.png";
- Response uploadResponse = UtilIT.uploadFileViaNative(String.valueOf(datasetId), pathToFile, apiToken);
+ Response uploadResponse = UtilIT.uploadFileViaNative(String.valueOf(datasetId), pathToFile, jsonObj, apiToken);
+ uploadResponse.prettyPrint();
uploadResponse.then().assertThat()
.statusCode(OK.getStatusCode());
Integer modifyFileId = UtilIT.getDataFileIdFromResponse(uploadResponse);
pathToFile = "src/main/webapp/resources/images/dataverseproject_logo.jpg";
- uploadResponse = UtilIT.uploadFileViaNative(String.valueOf(datasetId), pathToFile, apiToken);
+ uploadResponse = UtilIT.uploadFileViaNative(String.valueOf(datasetId), pathToFile, jsonObj, apiToken);
uploadResponse.then().assertThat()
.statusCode(OK.getStatusCode());
Integer deleteFileId = UtilIT.getDataFileIdFromResponse(uploadResponse);
pathToFile = "src/main/webapp/resources/images/fav/favicon-16x16.png";
- uploadResponse = UtilIT.uploadFileViaNative(String.valueOf(datasetId), pathToFile, apiToken);
+ uploadResponse = UtilIT.uploadFileViaNative(String.valueOf(datasetId), pathToFile, jsonObj, apiToken);
uploadResponse.then().assertThat()
.statusCode(OK.getStatusCode());
Integer replaceFileId = UtilIT.getDataFileIdFromResponse(uploadResponse);
@@ -5221,7 +5229,7 @@ public void testCompareDatasetVersionsAPI() throws InterruptedException {
// Test adding a file
pathToFile = "src/test/resources/tab/test.tab";
- Response uploadTabularFileResponse = UtilIT.uploadFileViaNative(Integer.toString(datasetId), pathToFile, Json.createObjectBuilder().build(), apiToken);
+ Response uploadTabularFileResponse = UtilIT.uploadFileViaNative(Integer.toString(datasetId), pathToFile, jsonObj, apiToken);
uploadTabularFileResponse.prettyPrint();
uploadTabularFileResponse.then().assertThat()
.statusCode(OK.getStatusCode());
@@ -5243,7 +5251,7 @@ public void testCompareDatasetVersionsAPI() throws InterruptedException {
.statusCode(NO_CONTENT.getStatusCode());
// Test Replacing a file
- Response replaceResponse = UtilIT.replaceFile(String.valueOf(replaceFileId), "src/main/webapp/resources/images/fav/favicon-32x32.png", apiToken);
+ Response replaceResponse = UtilIT.replaceFile(String.valueOf(replaceFileId), "src/main/webapp/resources/images/fav/favicon-32x32.png", jsonObj, apiToken);
replaceResponse.prettyPrint();
replaceResponse.then().assertThat()
.statusCode(OK.getStatusCode());
@@ -5273,6 +5281,8 @@ public void testCompareDatasetVersionsAPI() throws InterruptedException {
.body("data.metadataChanges[1].changed[0].oldValue", CoreMatchers.containsString(""))
.body("data.metadataChanges[1].changed[0].newValue", CoreMatchers.containsString("Parallel Group Design; Nested Case Control Design"))
.body("data.filesAdded[0].fileName", CoreMatchers.equalTo("test.tab"))
+ .body("data.filesAdded[0].filePath", CoreMatchers.equalTo("data/subdir1"))
+ .body("data.filesAdded[0].description", CoreMatchers.equalTo("my description"))
.body("data.filesAdded[0].tags[0]", CoreMatchers.equalTo("Survey"))
.body("data.filesRemoved[0].fileName", CoreMatchers.equalTo("dataverseproject_logo.jpg"))
.body("data.fileChanges[0].fileName", CoreMatchers.equalTo("dataverse-icon-1200.png"))
From d35ecc77d750ec7f233c0e739e9ab00a0083337d Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Thu, 24 Oct 2024 14:55:22 -0400
Subject: [PATCH 066/270] Update native-api.rst reference typo
---
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 c90f34db568..1381e9c3c4e 100644
--- a/doc/sphinx-guides/source/api/native-api.rst
+++ b/doc/sphinx-guides/source/api/native-api.rst
@@ -1970,7 +1970,7 @@ The fully expanded example above (without environment variables) looks like this
curl -H "X-Dataverse-key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X POST "https://demo.dataverse.org/api/datasets/24/previewUrl"
If Anonymized Access has been enabled on a Dataverse installation (see the :ref:`:AnonymizedFieldTypeNames` setting), an optional 'anonymizedAccess' query parameter is allowed.
-Setting anonymizedAccess=true in your call will create a PreviewURL that only allows an anonymized view of the Dataset (see :ref:`previewurl`).
+Setting anonymizedAccess=true in your call will create a PreviewURL that only allows an anonymized view of the Dataset (see :ref:`previewUrl`).
.. code-block:: bash
From c2350df536d94ade2d5405fca1e5396bd34e7d68 Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Thu, 24 Oct 2024 15:14:57 -0400
Subject: [PATCH 067/270] Update dataset-management.rst
---
doc/sphinx-guides/source/user/dataset-management.rst | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/doc/sphinx-guides/source/user/dataset-management.rst b/doc/sphinx-guides/source/user/dataset-management.rst
index 39fe0128dfd..b3a14554b40 100755
--- a/doc/sphinx-guides/source/user/dataset-management.rst
+++ b/doc/sphinx-guides/source/user/dataset-management.rst
@@ -169,7 +169,7 @@ Certain file types in the Dataverse installation are supported by additional fun
File Previews
-------------
-Dataverse installations can add previewers for common file types uploaded by their research communities. The previews appear on the file page. If a preview tool for a specific file type is available, the preview will be created and will display automatically, after terms have been agreed to or a guestbook entry has been made, if necessary. File previews are not available for restricted files unless they are being accessed using a Preview URL. See also :ref:`privateurl`.
+Dataverse installations can add previewers for common file types uploaded by their research communities. The previews appear on the file page. If a preview tool for a specific file type is available, the preview will be created and will display automatically, after terms have been agreed to or a guestbook entry has been made, if necessary. File previews are not available for restricted files unless they are being accessed using a Preview URL. See also :ref:`previewUrl`.
Previewers are available for the following file types:
@@ -676,7 +676,7 @@ Submit for Review
If you have a Contributor role (can edit metadata, upload files, and edit files, edit Terms, Guestbook, and submit datasets for review) in a Dataverse collection you can submit your dataset for review when you have finished uploading your files and filling in all of the relevant metadata fields. To submit your dataset for review, go to your dataset and click the "Submit for Review" button, which is located next to the "Edit" button on the upper-right. In the confirmation popup, you can review your selection of license (or custom terms, if available). Once you have confirmed the submission, the Admin or Curator for this Dataverse collection will be notified to review this dataset before they decide to either publish the dataset or click "Return to Author". If the dataset is published, the contributor will be notified that it is now published. If the dataset is returned to the author, the contributor of this dataset will be notified that they need to make modifications before it can be submitted for review again.
-.. _privateurl:
+.. _previewUrl:
Preview URL to Review Unpublished Dataset
=========================================
From f851163df6a17106599f675e205dd5985ca68c94 Mon Sep 17 00:00:00 2001
From: Ludovic DANIEL
Date: Fri, 25 Oct 2024 16:55:54 +0200
Subject: [PATCH 068/270] Ordering subfields while displaying dataset version
differences
---
.../iq/dataverse/DatasetVersionDifference.java | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java
index eca0c84ae84..c32f49e985e 100644
--- a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java
+++ b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java
@@ -152,8 +152,7 @@ public DatasetVersionDifference(DatasetVersion newVersion, DatasetVersion origin
getReplacedFiles();
initDatasetFilesDifferencesList();
- //Sort within blocks by datasetfieldtype dispaly order then....
- //sort via metadatablock order - citation first...
+ //Sort within blocks by datasetfieldtype display order
for (List blockList : detailDataByBlock) {
Collections.sort(blockList, (DatasetField[] l1, DatasetField[] l2) -> {
DatasetField dsfa = l1[0]; //(DatasetField[]) l1.get(0);
@@ -163,6 +162,17 @@ public DatasetVersionDifference(DatasetVersion newVersion, DatasetVersion origin
return Integer.valueOf(a).compareTo(b);
});
}
+ //Sort existing compoundValues by datasetfieldtype display order
+ for (List blockList : detailDataByBlock) {
+ for (DatasetField[] dfarr : blockList) {
+ for (DatasetField df : dfarr) {
+ for (DatasetFieldCompoundValue dfcv : df.getDatasetFieldCompoundValues()) {
+ Collections.sort(dfcv.getChildDatasetFields(), DatasetField.DisplayOrder);
+ }
+ }
+ }
+ }
+ //Sort via metadatablock order
Collections.sort(detailDataByBlock, (List l1, List l2) -> {
DatasetField dsfa[] = (DatasetField[]) l1.get(0);
DatasetField dsfb[] = (DatasetField[]) l2.get(0);
From b3147a083d140b6a9a2feea21aa043621f692fcc Mon Sep 17 00:00:00 2001
From: Ludovic DANIEL
Date: Fri, 25 Oct 2024 17:04:38 +0200
Subject: [PATCH 069/270] adding release note
---
doc/release-notes/10969-order-subfields-version-difference.md | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 doc/release-notes/10969-order-subfields-version-difference.md
diff --git a/doc/release-notes/10969-order-subfields-version-difference.md b/doc/release-notes/10969-order-subfields-version-difference.md
new file mode 100644
index 00000000000..3f245ebe069
--- /dev/null
+++ b/doc/release-notes/10969-order-subfields-version-difference.md
@@ -0,0 +1,2 @@
+Bug Fix:
+In order to facilitate the comparison between the draft version and the published version of a dataset, a sort on subfields has been added (#10969)
\ No newline at end of file
From 016fc9a1ada93fe8a8578ce2964e5c19ee231320 Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Tue, 29 Oct 2024 09:59:50 -0400
Subject: [PATCH 070/270] #8184 hide breadcrumbs/header for anon access
---
.../edu/harvard/iq/dataverse/DatasetPage.java | 15 ++++++++++++---
.../edu/harvard/iq/dataverse/util/BundleUtil.java | 2 +-
src/main/java/propertyFiles/Bundle.properties | 2 +-
src/main/webapp/dataset.xhtml | 3 ++-
4 files changed, 16 insertions(+), 6 deletions(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java
index 8522f2733c7..ce4fc531524 100644
--- a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java
+++ b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java
@@ -1935,13 +1935,13 @@ public void updateOwnerDataverse() {
if (selectedHostDataverse != null && selectedHostDataverse.getId() != null) {
ownerId = selectedHostDataverse.getId();
dataset.setOwner(selectedHostDataverse);
- logger.info("New host dataverse id: "+ownerId);
+ logger.info("New host dataverse id: " + ownerId);
// discard the dataset already created
//If a global ID was already assigned, as is true for direct upload, keep it (if files were already uploaded, they are at the path corresponding to the existing global id)
GlobalId gid = dataset.getGlobalId();
dataset = new Dataset();
- if(gid!=null) {
- dataset.setGlobalId(gid);
+ if (gid != null) {
+ dataset.setGlobalId(gid);
}
// initiate from scratch: (isolate the creation of a new dataset in its own method?)
@@ -2287,8 +2287,17 @@ private String init(boolean initFull) {
JsfHelper.addWarningMessage(message);
}
}
+ if(isAnonymizedAccess()){
+ dataverseHeaderFragment.setBreadcrumbs(new ArrayList<>());
+ }
return null;
}
+
+ public void viewActionInitBreadcrumbs(){
+ if(!isAnonymizedAccess()){
+ dataverseHeaderFragment.initBreadcrumbs(dataset);
+ }
+ }
private void displayWorkflowComments() {
List comments = workingVersion.getWorkflowComments();
diff --git a/src/main/java/edu/harvard/iq/dataverse/util/BundleUtil.java b/src/main/java/edu/harvard/iq/dataverse/util/BundleUtil.java
index 922e6ff5d28..771cf5fd0f0 100644
--- a/src/main/java/edu/harvard/iq/dataverse/util/BundleUtil.java
+++ b/src/main/java/edu/harvard/iq/dataverse/util/BundleUtil.java
@@ -111,7 +111,7 @@ public static ResourceBundle getResourceBundle(String propertyFileName, Locale c
ClassLoader loader = getClassLoader(filesRootDirectory);
bundle = ResourceBundle.getBundle(propertyFileName, currentLocale, loader);
} catch (MissingResourceException mre) {
- logger.warning("No property file named " + propertyFileName + "_" + currentLocale.getLanguage()
+ logger.fine("No property file named " + propertyFileName + "_" + currentLocale.getLanguage()
+ " found in " + filesRootDirectory + ", using untranslated values");
bundle = ResourceBundle.getBundle("propertyFiles/" + propertyFileName, currentLocale);
}
diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties
index f5098cb12aa..6924b927f0c 100644
--- a/src/main/java/propertyFiles/Bundle.properties
+++ b/src/main/java/propertyFiles/Bundle.properties
@@ -1721,7 +1721,7 @@ dataset.transferUnrestricted=Click Continue to transfer the elligible files.
dataset.requestAccessToRestrictedFiles=You may request access to any restricted file(s) by clicking the Request Access button.
dataset.requestAccessToRestrictedFilesWithEmbargo=Embargoed files cannot be accessed during the embargo period. If your selection contains restricted files, you may request access to them by clicking the Request Access button.
dataset.privateurl.infoMessageAuthor=Privately share this dataset before it is published: {0}
-dataset.privateurl.infoMessageReviewer=This unpublished dataset is being privately shared.
+dataset.privateurl.infoMessageReviewer=You are viewing a preview of this unpublished dataset version.
dataset.privateurl.header=Unpublished Dataset Preview URL
dataset.privateurl.tip=To cite this data in publications, use the dataset's persistent ID instead of this URL. For more information about the Preview URL feature, please refer to the User Guide.
dataset.privateurl.onlyone=Only one Preview URL can be active for a single dataset.
diff --git a/src/main/webapp/dataset.xhtml b/src/main/webapp/dataset.xhtml
index 86bb6adb4b4..94d14c055be 100644
--- a/src/main/webapp/dataset.xhtml
+++ b/src/main/webapp/dataset.xhtml
@@ -20,6 +20,7 @@
+
-
+
From e0a98bcbf25fe1228b10e1cb99118a72575c7d38 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Tue, 29 Oct 2024 14:11:27 -0400
Subject: [PATCH 071/270] fixing filter for Results By Username
---
src/main/webapp/mydata_fragment.xhtml | 6 +++---
src/main/webapp/resources/js/mydata.js | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/main/webapp/mydata_fragment.xhtml b/src/main/webapp/mydata_fragment.xhtml
index b3828578dca..d9e55b2eef2 100644
--- a/src/main/webapp/mydata_fragment.xhtml
+++ b/src/main/webapp/mydata_fragment.xhtml
@@ -100,10 +100,10 @@
-
+
#{bundle['mydataFragment.resultsByUserName']}
-
+
@@ -150,4 +150,4 @@
-
\ No newline at end of file
+
diff --git a/src/main/webapp/resources/js/mydata.js b/src/main/webapp/resources/js/mydata.js
index 899ba6637e2..c731d6772ac 100644
--- a/src/main/webapp/resources/js/mydata.js
+++ b/src/main/webapp/resources/js/mydata.js
@@ -391,7 +391,7 @@ function submit_my_data_search(){
// --------------------------------
// ah, but with the horribly coded xhtml page, we can't use form tags...
//var formData = $('#mydata_filter_form').serialize();
- var formData = $("#my_data_filter_column :input").serialize() + '&' + $("#my_data_filter_column2 :input").serialize() ;
+ var formData = $("#my_data_filter_column :input").serialize() + '&' + $("#my_data_filter_column3 :input").serialize()+ '&' + $("#my_data_filter_column2 :input").serialize() ;
// For debugging, show the search params
if (MYDATA_DEBUG_ON){
From f6a65c5803645f8be34a0f0b9b90b8a15d1a6c2c Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Tue, 29 Oct 2024 14:23:13 -0400
Subject: [PATCH 072/270] add release note
---
doc/release-notes/7239-mydata-results-by-username.md | 3 +++
1 file changed, 3 insertions(+)
create mode 100644 doc/release-notes/7239-mydata-results-by-username.md
diff --git a/doc/release-notes/7239-mydata-results-by-username.md b/doc/release-notes/7239-mydata-results-by-username.md
new file mode 100644
index 00000000000..7530783661c
--- /dev/null
+++ b/doc/release-notes/7239-mydata-results-by-username.md
@@ -0,0 +1,3 @@
+## Fix My Data filter results by username for Administrators
+
+The filtering for the username on the MyData page was not working. This is only available for Administrators. This fixes the "Results for Username" field to return the data for the desired user.
From 0daa077413492bd00d2e9ce1b99db9ff69922426 Mon Sep 17 00:00:00 2001
From: paulboon
Date: Wed, 30 Oct 2024 13:24:34 +0100
Subject: [PATCH 073/270] Fixed wrong character set conversion in
Shib.getValueFromAssertion
---
src/main/java/edu/harvard/iq/dataverse/Shib.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/Shib.java b/src/main/java/edu/harvard/iq/dataverse/Shib.java
index 38d732c6acd..121d03ef0c7 100644
--- a/src/main/java/edu/harvard/iq/dataverse/Shib.java
+++ b/src/main/java/edu/harvard/iq/dataverse/Shib.java
@@ -422,7 +422,7 @@ private String getValueFromAssertion(String key) {
if (attribute != null) {
String attributeValue = attribute.toString();
if(systemConfig.isShibAttributeCharacterSetConversionEnabled()) {
- attributeValue = new String(attributeValue.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.ISO_8859_1);
+ attributeValue = new String(attributeValue.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
}
String trimmedValue = attributeValue.trim();
if (!trimmedValue.isEmpty()) {
From fa904b6138f1faf3c6cd5f012637ca1515f30c09 Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Wed, 30 Oct 2024 10:15:17 -0400
Subject: [PATCH 074/270] #8184 acceptance testing
---
src/main/java/propertyFiles/Bundle.properties | 4 ++--
src/main/webapp/dataset.xhtml | 8 ++------
2 files changed, 4 insertions(+), 8 deletions(-)
diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties
index 6924b927f0c..2911af8f247 100644
--- a/src/main/java/propertyFiles/Bundle.properties
+++ b/src/main/java/propertyFiles/Bundle.properties
@@ -1727,12 +1727,12 @@ dataset.privateurl.tip=To cite this data in publications, use the dataset's pers
dataset.privateurl.onlyone=Only one Preview URL can be active for a single dataset.
dataset.privateurl.absent=Preview URL has not been created.
dataset.privateurl.general.button.label=Create General Preview URL
-dataset.privateurl.general.description=Create a URL that others can use to review this dataset version before it is published. they will be able to access all files in the dataset and see all metadata, including metadata that may identify the dataset's authors.
+dataset.privateurl.general.description=Create a URL that others can use to review this dataset version before it is published. They will be able to access all files in the dataset and see all metadata, including metadata that may identify the dataset's authors.
dataset.privateurl.general.title=General Preview
dataset.privateurl.anonymous.title=Anonymous Preview
dataset.privateurl.anonymous.button.label=Create Anonymous Preview URL
dataset.privateurl.anonymous.description=Create a URL that others can use to access an anonymized view of this unpublished dataset version. Metadata that could identify the dataset author will not be displayed. Non-identifying metadata will be visible.
-dataset.privateurl.anonymous.description.paragraph.two=The dataset's files are not changed and will be accessible if they're not restricted. Users of the preview URL will be able to see the name of the repository and the name of the collection that this dataset is in, which may expose the dataset author's identities.
+dataset.privateurl.anonymous.description.paragraph.two=The dataset's files are not changed and will be accessible if they're not restricted. Users of the preview URL will be able to see the name of the repository but not the name of the collection that this dataset is in.
dataset.privateurl.createPrivateUrl=Create Preview URL
dataset.privateurl.introduction=You can create a Preview URL to copy and share with others who will not need a repository account to review this unpublished dataset version. Once the dataset is published ot if the URL is disabled, the URL will no longer work and will point to a "Page not found" page.
dataset.privateurl.createPrivateUrl.anonymized=Create URL for Anonymized Access
diff --git a/src/main/webapp/dataset.xhtml b/src/main/webapp/dataset.xhtml
index 94d14c055be..cb2f8361742 100644
--- a/src/main/webapp/dataset.xhtml
+++ b/src/main/webapp/dataset.xhtml
@@ -1205,9 +1205,7 @@
} else {
selectText(this);
}">
-
From ad3505143e60e4b9cea896c52b3db782a77f2f07 Mon Sep 17 00:00:00 2001
From: Ludovic DANIEL
Date: Wed, 30 Oct 2024 17:39:50 +0100
Subject: [PATCH 075/270] add release note
---
doc/release-notes/10772-fix-importDDI-otherId.md | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 doc/release-notes/10772-fix-importDDI-otherId.md
diff --git a/doc/release-notes/10772-fix-importDDI-otherId.md b/doc/release-notes/10772-fix-importDDI-otherId.md
new file mode 100644
index 00000000000..d5a9018b2b2
--- /dev/null
+++ b/doc/release-notes/10772-fix-importDDI-otherId.md
@@ -0,0 +1,2 @@
+Bug Fix :
+This PR fixes the `edu.harvard.iq.dataverse.util.json.JsonParseException: incorrect multiple for field otherId` error when DDI harvested data contains multiple ortherId.
\ No newline at end of file
From a8dbae4e19e9d2ec963c6bacdfbb2f0f00c80482 Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Thu, 31 Oct 2024 09:54:15 -0400
Subject: [PATCH 076/270] #8184 update disable button labels
---
src/main/java/propertyFiles/Bundle.properties | 4 ++++
src/main/webapp/dataset.xhtml | 9 ++++++---
2 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties
index 2911af8f247..b5739ef7633 100644
--- a/src/main/java/propertyFiles/Bundle.properties
+++ b/src/main/java/propertyFiles/Bundle.properties
@@ -1738,7 +1738,11 @@ dataset.privateurl.introduction=You can create a Preview URL to copy and share w
dataset.privateurl.createPrivateUrl.anonymized=Create URL for Anonymized Access
dataset.privateurl.createPrivateUrl.anonymized.unavailable=Anonymized Access is not available once a version of the dataset has been published
dataset.privateurl.disablePrivateUrl=Disable Preview URL
+dataset.privateurl.disableGeneralPreviewUrl=Disable General Preview URL
+dataset.privateurl.disableAnonPreviewUrl=Disable Anonymous Preview URL
dataset.privateurl.disablePrivateUrlConfirm=Yes, Disable Preview URL
+dataset.privateurl.disableGeneralPreviewUrlConfirm=Yes, Disable General Preview URL
+dataset.privateurl.disableAnonPreviewUrlConfirm=Yes, Disable Anonymous Preview URL
dataset.privateurl.disableConfirmationText=Are you sure you want to disable the Preview URL? If you have shared the Preview URL with others they will no longer be able to use it to access your unpublished dataset.
dataset.privateurl.cannotCreate=Preview URL can only be used with unpublished versions of datasets.
dataset.privateurl.roleassigeeTitle=Preview URL Enabled
diff --git a/src/main/webapp/dataset.xhtml b/src/main/webapp/dataset.xhtml
index cb2f8361742..ee69f76cca2 100644
--- a/src/main/webapp/dataset.xhtml
+++ b/src/main/webapp/dataset.xhtml
@@ -1215,7 +1215,7 @@
#{bundle['copyClipboard']}
-
+
-
+
+
+
+
#{bundle.cancel}
From 42ac8c01a03deed86ef67722c2c77ebd7a49192e Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Thu, 31 Oct 2024 10:11:34 -0400
Subject: [PATCH 077/270] #8184 update popup message per Julian
---
src/main/java/propertyFiles/Bundle.properties | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties
index b5739ef7633..d03b15937b8 100644
--- a/src/main/java/propertyFiles/Bundle.properties
+++ b/src/main/java/propertyFiles/Bundle.properties
@@ -1732,7 +1732,7 @@ dataset.privateurl.general.title=General Preview
dataset.privateurl.anonymous.title=Anonymous Preview
dataset.privateurl.anonymous.button.label=Create Anonymous Preview URL
dataset.privateurl.anonymous.description=Create a URL that others can use to access an anonymized view of this unpublished dataset version. Metadata that could identify the dataset author will not be displayed. Non-identifying metadata will be visible.
-dataset.privateurl.anonymous.description.paragraph.two=The dataset's files are not changed and will be accessible if they're not restricted. Users of the preview URL will be able to see the name of the repository but not the name of the collection that this dataset is in.
+dataset.privateurl.anonymous.description.paragraph.two=The dataset's files are not changed and will be accessible if they're not restricted. Users of the Anonymous Preview URL will not be able to see the name of the Dataverse that this dataset is in but will be able to see the name of the repository, which might expose the dataset authors' identities.
dataset.privateurl.createPrivateUrl=Create Preview URL
dataset.privateurl.introduction=You can create a Preview URL to copy and share with others who will not need a repository account to review this unpublished dataset version. Once the dataset is published ot if the URL is disabled, the URL will no longer work and will point to a "Page not found" page.
dataset.privateurl.createPrivateUrl.anonymized=Create URL for Anonymized Access
From fea4b640a127786208b9dbe66d2b179c1c39b531 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Thu, 31 Oct 2024 15:52:37 -0400
Subject: [PATCH 078/270] Update
doc/release-notes/7239-mydata-results-by-username.md
Co-authored-by: Philip Durbin
---
doc/release-notes/7239-mydata-results-by-username.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/release-notes/7239-mydata-results-by-username.md b/doc/release-notes/7239-mydata-results-by-username.md
index 7530783661c..fa1ce56d89e 100644
--- a/doc/release-notes/7239-mydata-results-by-username.md
+++ b/doc/release-notes/7239-mydata-results-by-username.md
@@ -1,3 +1,3 @@
## Fix My Data filter results by username for Administrators
-The filtering for the username on the MyData page was not working. This is only available for Administrators. This fixes the "Results for Username" field to return the data for the desired user.
+The filtering for the username on the MyData page was not working. This is only available for superusers. This fixes the "Results for Username" field to return the data for the desired user. See also #7239 and #10980.
From 5456803d4405d6e5dda2b9b8dd4c18e1605abb35 Mon Sep 17 00:00:00 2001
From: Don Sizemore
Date: Fri, 1 Nov 2024 10:31:33 -0400
Subject: [PATCH 079/270] #10889 Update
doc/release-notes/10889_bump_PG17_FlyWay10.md
Co-authored-by: Philip Durbin
---
doc/release-notes/10889_bump_PG17_FlyWay10.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/doc/release-notes/10889_bump_PG17_FlyWay10.md b/doc/release-notes/10889_bump_PG17_FlyWay10.md
index 0f74568e5cd..7bb509886fb 100644
--- a/doc/release-notes/10889_bump_PG17_FlyWay10.md
+++ b/doc/release-notes/10889_bump_PG17_FlyWay10.md
@@ -1,3 +1,7 @@
This release bumps both the Postgres JDBC driver and Flyway versions. This should better support Postgres version 17, and as of version 10 Flyway no longer requires a paid subscription to support older versions of Postgres.
While we don't encourage the use of older Postgres versions, this flexibility may benefit some of our long-standing installations in their upgrade paths. Postgres 13 remains the version used with automated testing.
+
+As part of this update, the containerized development environment now uses Postgres 17 instead of 16. Worst case, developers can start with a fresh database, if necessary.
+
+The Docker compose file used for [evaluations or demos](https://dataverse-guide--10912.org.readthedocs.build/en/10912/container/running/demo.html) has been upgraded from Postgres 13 to 17.
From c094c8a6e45d02cac5c136e5b13bd5de113465bc Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Fri, 1 Nov 2024 16:37:39 -0400
Subject: [PATCH 080/270] github actions from v3 to v4
---
.github/workflows/container_app_pr.yml | 6 +++---
.github/workflows/container_app_push.yml | 2 +-
.github/workflows/deploy_beta_testing.yml | 10 +++++-----
.github/workflows/guides_build_sphinx.yml | 2 +-
.github/workflows/maven_unit_test.yml | 22 +++++++++++-----------
.github/workflows/reviewdog_checkstyle.yml | 2 +-
.github/workflows/shellcheck.yml | 2 +-
.github/workflows/shellspec.yml | 6 +++---
.github/workflows/spi_release.yml | 14 +++++++-------
9 files changed, 33 insertions(+), 33 deletions(-)
diff --git a/.github/workflows/container_app_pr.yml b/.github/workflows/container_app_pr.yml
index c86d284e74b..a3468cbc220 100644
--- a/.github/workflows/container_app_pr.yml
+++ b/.github/workflows/container_app_pr.yml
@@ -20,14 +20,14 @@ jobs:
if: ${{ github.repository_owner == 'IQSS' }}
steps:
# Checkout the pull request code as when merged
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
ref: 'refs/pull/${{ github.event.client_payload.pull_request.number }}/merge'
- - uses: actions/setup-java@v3
+ - uses: actions/setup-java@v4
with:
java-version: "17"
distribution: 'adopt'
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
diff --git a/.github/workflows/container_app_push.yml b/.github/workflows/container_app_push.yml
index 3b7ce066d73..184b69583a5 100644
--- a/.github/workflows/container_app_push.yml
+++ b/.github/workflows/container_app_push.yml
@@ -68,7 +68,7 @@ jobs:
if: ${{ github.event_name != 'pull_request' && github.ref_name == 'develop' && github.repository_owner == 'IQSS' }}
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: peter-evans/dockerhub-description@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
diff --git a/.github/workflows/deploy_beta_testing.yml b/.github/workflows/deploy_beta_testing.yml
index 028f0140cc9..87eb6e8c150 100644
--- a/.github/workflows/deploy_beta_testing.yml
+++ b/.github/workflows/deploy_beta_testing.yml
@@ -10,9 +10,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4.1.7
- - uses: actions/setup-java@v3
+ - uses: actions/setup-java@v4.1.7
with:
distribution: 'zulu'
java-version: '17'
@@ -32,7 +32,7 @@ jobs:
run: echo "war_file=$(ls *.war | head -1)">> $GITHUB_ENV
- name: Upload war artifact
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4.1.7
with:
name: built-app
path: ./target/${{ env.war_file }}
@@ -42,10 +42,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4.1.7
- name: Download war artifact
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@v4.1.7
with:
name: built-app
path: ./
diff --git a/.github/workflows/guides_build_sphinx.yml b/.github/workflows/guides_build_sphinx.yml
index 86b59b11d35..fa3a876c418 100644
--- a/.github/workflows/guides_build_sphinx.yml
+++ b/.github/workflows/guides_build_sphinx.yml
@@ -10,7 +10,7 @@ jobs:
docs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- uses: uncch-rdmc/sphinx-action@master
with:
docs-folder: "doc/sphinx-guides/"
diff --git a/.github/workflows/maven_unit_test.yml b/.github/workflows/maven_unit_test.yml
index a94b17a67ba..5c7a154e2f5 100644
--- a/.github/workflows/maven_unit_test.yml
+++ b/.github/workflows/maven_unit_test.yml
@@ -32,9 +32,9 @@ jobs:
steps:
# TODO: As part of #10618 change to setup-maven custom action
# Basic setup chores
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4.1.7
- name: Set up JDK ${{ matrix.jdk }}
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v4.1.7
with:
java-version: ${{ matrix.jdk }}
distribution: temurin
@@ -57,7 +57,7 @@ jobs:
# Upload the built war file. For download, it will be wrapped in a ZIP by GitHub.
# See also https://github.com/actions/upload-artifact#zipped-artifact-downloads
- - uses: actions/upload-artifact@v3
+ - uses: actions/upload-artifact@v4.1.7
with:
name: dataverse-java${{ matrix.jdk }}.war
path: target/dataverse*.war
@@ -67,7 +67,7 @@ jobs:
- run: |
tar -cvf java-builddir.tar target
tar -cvf java-m2-selection.tar ~/.m2/repository/io/gdcc/dataverse-*
- - uses: actions/upload-artifact@v3
+ - uses: actions/upload-artifact@v4.1.7
with:
name: java-artifacts
path: |
@@ -98,16 +98,16 @@ jobs:
steps:
# TODO: As part of #10618 change to setup-maven custom action
# Basic setup chores
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4.1.7
- name: Set up JDK ${{ matrix.jdk }}
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v4.1.7
with:
java-version: ${{ matrix.jdk }}
distribution: temurin
cache: maven
# Get the build output from the unit test job
- - uses: actions/download-artifact@v3
+ - uses: actions/download-artifact@v4.1.7
with:
name: java-artifacts
- run: |
@@ -119,7 +119,7 @@ jobs:
# Wrap up and send to coverage job
- run: tar -cvf java-reportdir.tar target/site
- - uses: actions/upload-artifact@v3
+ - uses: actions/upload-artifact@v4.1.7
with:
name: java-reportdir
path: java-reportdir.tar
@@ -132,15 +132,15 @@ jobs:
steps:
# TODO: As part of #10618 change to setup-maven custom action
# Basic setup chores
- - uses: actions/checkout@v3
- - uses: actions/setup-java@v3
+ - uses: actions/checkout@v4.1.7
+ - uses: actions/setup-java@v4.1.7
with:
java-version: '17'
distribution: temurin
cache: maven
# Get the build output from the integration test job
- - uses: actions/download-artifact@v3
+ - uses: actions/download-artifact@v4.1.7
with:
name: java-reportdir
- run: tar -xvf java-reportdir.tar
diff --git a/.github/workflows/reviewdog_checkstyle.yml b/.github/workflows/reviewdog_checkstyle.yml
index 90a0dd7d06b..804b04f696a 100644
--- a/.github/workflows/reviewdog_checkstyle.yml
+++ b/.github/workflows/reviewdog_checkstyle.yml
@@ -10,7 +10,7 @@ jobs:
name: Checkstyle job
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Run check style
uses: nikitasavinov/checkstyle-action@master
with:
diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml
index 56f7d648dc4..fb9cf5a0a1f 100644
--- a/.github/workflows/shellcheck.yml
+++ b/.github/workflows/shellcheck.yml
@@ -21,7 +21,7 @@ jobs:
permissions:
pull-requests: write
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: shellcheck
uses: reviewdog/action-shellcheck@v1
with:
diff --git a/.github/workflows/shellspec.yml b/.github/workflows/shellspec.yml
index 3320d9d08a4..cc09992edac 100644
--- a/.github/workflows/shellspec.yml
+++ b/.github/workflows/shellspec.yml
@@ -19,7 +19,7 @@ jobs:
steps:
- name: Install shellspec
run: curl -fsSL https://git.io/shellspec | sh -s ${{ env.SHELLSPEC_VERSION }} --yes
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Run Shellspec
run: |
cd tests/shell
@@ -30,7 +30,7 @@ jobs:
container:
image: rockylinux/rockylinux:9
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Install shellspec
run: |
curl -fsSL https://github.com/shellspec/shellspec/releases/download/${{ env.SHELLSPEC_VERSION }}/shellspec-dist.tar.gz | tar -xz -C /usr/share
@@ -47,7 +47,7 @@ jobs:
steps:
- name: Install shellspec
run: curl -fsSL https://git.io/shellspec | sh -s 0.28.1 --yes
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Run Shellspec
run: |
cd tests/shell
diff --git a/.github/workflows/spi_release.yml b/.github/workflows/spi_release.yml
index 8ad74b3e4bb..6398edca412 100644
--- a/.github/workflows/spi_release.yml
+++ b/.github/workflows/spi_release.yml
@@ -37,15 +37,15 @@ jobs:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' && needs.check-secrets.outputs.available == 'true'
steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-java@v3
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'adopt'
server-id: ossrh
server-username: MAVEN_USERNAME
server-password: MAVEN_PASSWORD
- - uses: actions/cache@v2
+ - uses: actions/cache@v4
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
@@ -63,12 +63,12 @@ jobs:
runs-on: ubuntu-latest
if: github.event_name == 'push' && needs.check-secrets.outputs.available == 'true'
steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-java@v3
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'adopt'
- - uses: actions/cache@v2
+ - uses: actions/cache@v4
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
@@ -76,7 +76,7 @@ jobs:
# Running setup-java again overwrites the settings.xml - IT'S MANDATORY TO DO THIS SECOND SETUP!!!
- name: Set up Maven Central Repository
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'adopt'
From 12c1597c5f788f6d23ee1035d9f996af4749a003 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Fri, 1 Nov 2024 16:45:26 -0400
Subject: [PATCH 081/270] Modify test file to force workflow to build and test
DO NOT MERGEgit add .git add .
---
src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
index 93f1024ae7a..db1e6eb5169 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
@@ -70,7 +70,8 @@ public class DatasetsIT {
@BeforeAll
public static void setUpClass() {
-
+ // !!!!!!!! DO NOT CHECK THIS FILE IN
+ logger.warning(">>>>>>>>>>> Just a line change to force GitHub actions to build and test Dataverse !!!!!!!");
RestAssured.baseURI = UtilIT.getRestAssuredBaseUri();
From 570d1285ba5f4a39596cec88295d8a45acc2e08c Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Fri, 1 Nov 2024 16:49:32 -0400
Subject: [PATCH 082/270] Modify test file to force workflow to build and test
DO NOT MERGE
---
.github/workflows/deploy_beta_testing.yml | 6 +++---
.github/workflows/maven_unit_test.yml | 12 ++++++------
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/.github/workflows/deploy_beta_testing.yml b/.github/workflows/deploy_beta_testing.yml
index 87eb6e8c150..c6a8bd909df 100644
--- a/.github/workflows/deploy_beta_testing.yml
+++ b/.github/workflows/deploy_beta_testing.yml
@@ -10,9 +10,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4.1.7
+ - uses: actions/checkout@v4
- - uses: actions/setup-java@v4.1.7
+ - uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
@@ -42,7 +42,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4.1.7
+ - uses: actions/checkout@v4
- name: Download war artifact
uses: actions/download-artifact@v4.1.7
diff --git a/.github/workflows/maven_unit_test.yml b/.github/workflows/maven_unit_test.yml
index 5c7a154e2f5..67f20bbdc93 100644
--- a/.github/workflows/maven_unit_test.yml
+++ b/.github/workflows/maven_unit_test.yml
@@ -32,9 +32,9 @@ jobs:
steps:
# TODO: As part of #10618 change to setup-maven custom action
# Basic setup chores
- - uses: actions/checkout@v4.1.7
+ - uses: actions/checkout@v4
- name: Set up JDK ${{ matrix.jdk }}
- uses: actions/setup-java@v4.1.7
+ uses: actions/setup-java@v4
with:
java-version: ${{ matrix.jdk }}
distribution: temurin
@@ -98,9 +98,9 @@ jobs:
steps:
# TODO: As part of #10618 change to setup-maven custom action
# Basic setup chores
- - uses: actions/checkout@v4.1.7
+ - uses: actions/checkout@v4
- name: Set up JDK ${{ matrix.jdk }}
- uses: actions/setup-java@v4.1.7
+ uses: actions/setup-java@v4
with:
java-version: ${{ matrix.jdk }}
distribution: temurin
@@ -132,8 +132,8 @@ jobs:
steps:
# TODO: As part of #10618 change to setup-maven custom action
# Basic setup chores
- - uses: actions/checkout@v4.1.7
- - uses: actions/setup-java@v4.1.7
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
with:
java-version: '17'
distribution: temurin
From 534ecdc0ab8834973d20c061fb84dc3a4e098de3 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Fri, 1 Nov 2024 16:55:09 -0400
Subject: [PATCH 083/270] Modify test file to force workflow to build and test
DO NOT MERGE
---
.github/workflows/deploy_beta_testing.yml | 2 +-
.github/workflows/maven_unit_test.yml | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/deploy_beta_testing.yml b/.github/workflows/deploy_beta_testing.yml
index c6a8bd909df..c36a4dfab43 100644
--- a/.github/workflows/deploy_beta_testing.yml
+++ b/.github/workflows/deploy_beta_testing.yml
@@ -32,7 +32,7 @@ jobs:
run: echo "war_file=$(ls *.war | head -1)">> $GITHUB_ENV
- name: Upload war artifact
- uses: actions/upload-artifact@v4.1.7
+ uses: actions/upload-artifact@v4
with:
name: built-app
path: ./target/${{ env.war_file }}
diff --git a/.github/workflows/maven_unit_test.yml b/.github/workflows/maven_unit_test.yml
index 67f20bbdc93..18a741e5ca5 100644
--- a/.github/workflows/maven_unit_test.yml
+++ b/.github/workflows/maven_unit_test.yml
@@ -57,7 +57,7 @@ jobs:
# Upload the built war file. For download, it will be wrapped in a ZIP by GitHub.
# See also https://github.com/actions/upload-artifact#zipped-artifact-downloads
- - uses: actions/upload-artifact@v4.1.7
+ - uses: actions/upload-artifact@v4
with:
name: dataverse-java${{ matrix.jdk }}.war
path: target/dataverse*.war
@@ -67,7 +67,7 @@ jobs:
- run: |
tar -cvf java-builddir.tar target
tar -cvf java-m2-selection.tar ~/.m2/repository/io/gdcc/dataverse-*
- - uses: actions/upload-artifact@v4.1.7
+ - uses: actions/upload-artifact@v4
with:
name: java-artifacts
path: |
@@ -119,7 +119,7 @@ jobs:
# Wrap up and send to coverage job
- run: tar -cvf java-reportdir.tar target/site
- - uses: actions/upload-artifact@v4.1.7
+ - uses: actions/upload-artifact@v4
with:
name: java-reportdir
path: java-reportdir.tar
From 5f19030e77861bf0ca92f9dc9ed8f91760ac21b8 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Fri, 1 Nov 2024 17:00:13 -0400
Subject: [PATCH 084/270] Modify test file to force workflow to build and test
DO NOT MERGE
---
conf/solr/update-fields.sh | 1 +
1 file changed, 1 insertion(+)
diff --git a/conf/solr/update-fields.sh b/conf/solr/update-fields.sh
index 386c1ee4e87..5c896a98a04 100755
--- a/conf/solr/update-fields.sh
+++ b/conf/solr/update-fields.sh
@@ -22,6 +22,7 @@ COPY_FIELDS=""
TRIGGER_CHAIN=0
ED_DELETE_FIELDS="'a+,'b-d"
ED_DELETE_COPYFIELDS="'a+,'b-d"
+FAKEDONOTMERGE=0
SOLR_SCHEMA_FIELD_BEGIN_MARK="SCHEMA-FIELDS::BEGIN"
SOLR_SCHEMA_FIELD_END_MARK="SCHEMA-FIELDS::END"
From caa8bdef67fb137a593b0ce0a490c5077b811b60 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Fri, 1 Nov 2024 17:03:03 -0400
Subject: [PATCH 085/270] Modify test file to force workflow to build and test
DO NOT MERGE
---
.github/workflows/shellcheck.yml | 2 +-
.github/workflows/shellspec.yml | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml
index fb9cf5a0a1f..d83c0a5315b 100644
--- a/.github/workflows/shellcheck.yml
+++ b/.github/workflows/shellcheck.yml
@@ -21,7 +21,7 @@ jobs:
permissions:
pull-requests: write
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v2
- name: shellcheck
uses: reviewdog/action-shellcheck@v1
with:
diff --git a/.github/workflows/shellspec.yml b/.github/workflows/shellspec.yml
index cc09992edac..3320d9d08a4 100644
--- a/.github/workflows/shellspec.yml
+++ b/.github/workflows/shellspec.yml
@@ -19,7 +19,7 @@ jobs:
steps:
- name: Install shellspec
run: curl -fsSL https://git.io/shellspec | sh -s ${{ env.SHELLSPEC_VERSION }} --yes
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v2
- name: Run Shellspec
run: |
cd tests/shell
@@ -30,7 +30,7 @@ jobs:
container:
image: rockylinux/rockylinux:9
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v2
- name: Install shellspec
run: |
curl -fsSL https://github.com/shellspec/shellspec/releases/download/${{ env.SHELLSPEC_VERSION }}/shellspec-dist.tar.gz | tar -xz -C /usr/share
@@ -47,7 +47,7 @@ jobs:
steps:
- name: Install shellspec
run: curl -fsSL https://git.io/shellspec | sh -s 0.28.1 --yes
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v2
- name: Run Shellspec
run: |
cd tests/shell
From 25d0caca81999859ad4cad65f71113dbfdbb9951 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Fri, 1 Nov 2024 17:03:40 -0400
Subject: [PATCH 086/270] Modify test file to force workflow to build and test
DO NOT MERGE
---
.github/workflows/shellcheck.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml
index d83c0a5315b..56f7d648dc4 100644
--- a/.github/workflows/shellcheck.yml
+++ b/.github/workflows/shellcheck.yml
@@ -21,7 +21,7 @@ jobs:
permissions:
pull-requests: write
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: shellcheck
uses: reviewdog/action-shellcheck@v1
with:
From 74fe482e7dc6d4663e08fa9756b7e76ed58c93a4 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Fri, 1 Nov 2024 17:05:53 -0400
Subject: [PATCH 087/270] Modify test file to force workflow to build and test
DO NOT MERGE
---
.github/workflows/spi_release.yml | 14 +++++++-------
conf/solr/update-fields.sh | 1 -
2 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/.github/workflows/spi_release.yml b/.github/workflows/spi_release.yml
index 6398edca412..8ad74b3e4bb 100644
--- a/.github/workflows/spi_release.yml
+++ b/.github/workflows/spi_release.yml
@@ -37,15 +37,15 @@ jobs:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' && needs.check-secrets.outputs.available == 'true'
steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-java@v4
+ - uses: actions/checkout@v3
+ - uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'adopt'
server-id: ossrh
server-username: MAVEN_USERNAME
server-password: MAVEN_PASSWORD
- - uses: actions/cache@v4
+ - uses: actions/cache@v2
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
@@ -63,12 +63,12 @@ jobs:
runs-on: ubuntu-latest
if: github.event_name == 'push' && needs.check-secrets.outputs.available == 'true'
steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-java@v4
+ - uses: actions/checkout@v3
+ - uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'adopt'
- - uses: actions/cache@v4
+ - uses: actions/cache@v2
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
@@ -76,7 +76,7 @@ jobs:
# Running setup-java again overwrites the settings.xml - IT'S MANDATORY TO DO THIS SECOND SETUP!!!
- name: Set up Maven Central Repository
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'adopt'
diff --git a/conf/solr/update-fields.sh b/conf/solr/update-fields.sh
index 5c896a98a04..386c1ee4e87 100755
--- a/conf/solr/update-fields.sh
+++ b/conf/solr/update-fields.sh
@@ -22,7 +22,6 @@ COPY_FIELDS=""
TRIGGER_CHAIN=0
ED_DELETE_FIELDS="'a+,'b-d"
ED_DELETE_COPYFIELDS="'a+,'b-d"
-FAKEDONOTMERGE=0
SOLR_SCHEMA_FIELD_BEGIN_MARK="SCHEMA-FIELDS::BEGIN"
SOLR_SCHEMA_FIELD_END_MARK="SCHEMA-FIELDS::END"
From ccee96af5021e31ff7eefaeeda655b93f3c26bab Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Fri, 1 Nov 2024 17:07:14 -0400
Subject: [PATCH 088/270] Modify test file to force workflow to build and test
DO NOT MERGE
---
.github/workflows/guides_build_sphinx.yml | 2 +-
.github/workflows/reviewdog_checkstyle.yml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/guides_build_sphinx.yml b/.github/workflows/guides_build_sphinx.yml
index fa3a876c418..86b59b11d35 100644
--- a/.github/workflows/guides_build_sphinx.yml
+++ b/.github/workflows/guides_build_sphinx.yml
@@ -10,7 +10,7 @@ jobs:
docs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v2
- uses: uncch-rdmc/sphinx-action@master
with:
docs-folder: "doc/sphinx-guides/"
diff --git a/.github/workflows/reviewdog_checkstyle.yml b/.github/workflows/reviewdog_checkstyle.yml
index 804b04f696a..90a0dd7d06b 100644
--- a/.github/workflows/reviewdog_checkstyle.yml
+++ b/.github/workflows/reviewdog_checkstyle.yml
@@ -10,7 +10,7 @@ jobs:
name: Checkstyle job
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v2
- name: Run check style
uses: nikitasavinov/checkstyle-action@master
with:
From 9263b8a8aea13ad420a5b7a7db360e48ab1561c0 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Fri, 1 Nov 2024 17:08:45 -0400
Subject: [PATCH 089/270] Modify test file to force workflow to build and test
DO NOT MERGE
---
.github/workflows/container_app_pr.yml | 6 +++---
.github/workflows/container_app_push.yml | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/container_app_pr.yml b/.github/workflows/container_app_pr.yml
index a3468cbc220..c86d284e74b 100644
--- a/.github/workflows/container_app_pr.yml
+++ b/.github/workflows/container_app_pr.yml
@@ -20,14 +20,14 @@ jobs:
if: ${{ github.repository_owner == 'IQSS' }}
steps:
# Checkout the pull request code as when merged
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v3
with:
ref: 'refs/pull/${{ github.event.client_payload.pull_request.number }}/merge'
- - uses: actions/setup-java@v4
+ - uses: actions/setup-java@v3
with:
java-version: "17"
distribution: 'adopt'
- - uses: actions/cache@v4
+ - uses: actions/cache@v3
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
diff --git a/.github/workflows/container_app_push.yml b/.github/workflows/container_app_push.yml
index 184b69583a5..3b7ce066d73 100644
--- a/.github/workflows/container_app_push.yml
+++ b/.github/workflows/container_app_push.yml
@@ -68,7 +68,7 @@ jobs:
if: ${{ github.event_name != 'pull_request' && github.ref_name == 'develop' && github.repository_owner == 'IQSS' }}
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v3
- uses: peter-evans/dockerhub-description@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
From d719dde6b3a7274621bcafc2257607e012acfb6c Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Fri, 1 Nov 2024 17:20:05 -0400
Subject: [PATCH 090/270] remove test file
---
src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
index db1e6eb5169..ce123b60b13 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
@@ -70,8 +70,6 @@ public class DatasetsIT {
@BeforeAll
public static void setUpClass() {
- // !!!!!!!! DO NOT CHECK THIS FILE IN
- logger.warning(">>>>>>>>>>> Just a line change to force GitHub actions to build and test Dataverse !!!!!!!");
RestAssured.baseURI = UtilIT.getRestAssuredBaseUri();
From 37f44b52a5435141c4dd43b8155222f0746802d9 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Fri, 1 Nov 2024 17:21:42 -0400
Subject: [PATCH 091/270] remove test file
---
src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
index ce123b60b13..c7184f0c78b 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
@@ -70,7 +70,8 @@ public class DatasetsIT {
@BeforeAll
public static void setUpClass() {
-
+
+
RestAssured.baseURI = UtilIT.getRestAssuredBaseUri();
Response removeIdentifierGenerationStyle = UtilIT.deleteSetting(SettingsServiceBean.Key.IdentifierGenerationStyle);
From a094d81f36c12cdd65f0f48656ed7948d3245b0d Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Fri, 1 Nov 2024 17:27:01 -0400
Subject: [PATCH 092/270] remove test file
---
src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
index c7184f0c78b..93f1024ae7a 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
@@ -70,8 +70,8 @@ public class DatasetsIT {
@BeforeAll
public static void setUpClass() {
-
-
+
+
RestAssured.baseURI = UtilIT.getRestAssuredBaseUri();
Response removeIdentifierGenerationStyle = UtilIT.deleteSetting(SettingsServiceBean.Key.IdentifierGenerationStyle);
From cf1f18dc3cdb4d008ecdb4ba43bc05aa2ceee0ab Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Mon, 4 Nov 2024 11:41:02 -0500
Subject: [PATCH 093/270] fix backwards newest oldest sort order
---
src/main/webapp/filesFragment.xhtml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/main/webapp/filesFragment.xhtml b/src/main/webapp/filesFragment.xhtml
index 117710cfd53..154700f7cf4 100644
--- a/src/main/webapp/filesFragment.xhtml
+++ b/src/main/webapp/filesFragment.xhtml
@@ -301,24 +301,24 @@
-
+
+
-
+
-
From 391a249171736b9ac811da7b198af443d6f549bd Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Mon, 4 Nov 2024 13:42:40 -0500
Subject: [PATCH 094/270] adding release note
---
doc/release-notes/10742-newest-oldest-sort-order-backwards.md | 3 +++
1 file changed, 3 insertions(+)
create mode 100644 doc/release-notes/10742-newest-oldest-sort-order-backwards.md
diff --git a/doc/release-notes/10742-newest-oldest-sort-order-backwards.md b/doc/release-notes/10742-newest-oldest-sort-order-backwards.md
new file mode 100644
index 00000000000..0afaf45449d
--- /dev/null
+++ b/doc/release-notes/10742-newest-oldest-sort-order-backwards.md
@@ -0,0 +1,3 @@
+## Minor bug fix to UI to fix the order of the files on the Dataset Files page when ordering by Date
+
+A fix was made to the ui to fix the ordering 'Newest' and 'Oldest' which were reversed
From 40abc7e15230eac599b66d44c5f953973b391928 Mon Sep 17 00:00:00 2001
From: Don Sizemore
Date: Mon, 4 Nov 2024 14:54:20 -0500
Subject: [PATCH 095/270] Update doc/release-notes/10889_bump_PG17_FlyWay10.md
describe zapping database in Docker environment
Co-authored-by: Philip Durbin
---
doc/release-notes/10889_bump_PG17_FlyWay10.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/release-notes/10889_bump_PG17_FlyWay10.md b/doc/release-notes/10889_bump_PG17_FlyWay10.md
index 7bb509886fb..c35b083fcd4 100644
--- a/doc/release-notes/10889_bump_PG17_FlyWay10.md
+++ b/doc/release-notes/10889_bump_PG17_FlyWay10.md
@@ -2,6 +2,6 @@ This release bumps both the Postgres JDBC driver and Flyway versions. This shoul
While we don't encourage the use of older Postgres versions, this flexibility may benefit some of our long-standing installations in their upgrade paths. Postgres 13 remains the version used with automated testing.
-As part of this update, the containerized development environment now uses Postgres 17 instead of 16. Worst case, developers can start with a fresh database, if necessary.
+As part of this update, the containerized development environment now uses Postgres 17 instead of 16. Developers must delete their data (`rm -rf docker-dev-volumes`) and start an empty database. They can rerun the quickstart in the dev guide.
The Docker compose file used for [evaluations or demos](https://dataverse-guide--10912.org.readthedocs.build/en/10912/container/running/demo.html) has been upgraded from Postgres 13 to 17.
From 59be2a8ccb63874d89c1f82e456a1ee2c1da98a5 Mon Sep 17 00:00:00 2001
From: Philip Durbin
Date: Tue, 5 Nov 2024 08:34:45 -0500
Subject: [PATCH 096/270] Update doc/release-notes/10889_bump_PG17_FlyWay10.md
---
doc/release-notes/10889_bump_PG17_FlyWay10.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/release-notes/10889_bump_PG17_FlyWay10.md b/doc/release-notes/10889_bump_PG17_FlyWay10.md
index c35b083fcd4..932c06fbc3d 100644
--- a/doc/release-notes/10889_bump_PG17_FlyWay10.md
+++ b/doc/release-notes/10889_bump_PG17_FlyWay10.md
@@ -2,6 +2,6 @@ This release bumps both the Postgres JDBC driver and Flyway versions. This shoul
While we don't encourage the use of older Postgres versions, this flexibility may benefit some of our long-standing installations in their upgrade paths. Postgres 13 remains the version used with automated testing.
-As part of this update, the containerized development environment now uses Postgres 17 instead of 16. Developers must delete their data (`rm -rf docker-dev-volumes`) and start an empty database. They can rerun the quickstart in the dev guide.
+As part of this update, the containerized development environment now uses Postgres 17 instead of 16. Developers must delete their data (`rm -rf docker-dev-volumes`) and start with an empty database. They can rerun the quickstart in the dev guide.
The Docker compose file used for [evaluations or demos](https://dataverse-guide--10912.org.readthedocs.build/en/10912/container/running/demo.html) has been upgraded from Postgres 13 to 17.
From 79c8cf38888563c8a7de40c99316a3ed8acbd192 Mon Sep 17 00:00:00 2001
From: Philip Durbin
Date: Tue, 5 Nov 2024 17:26:17 -0500
Subject: [PATCH 097/270] docs "Update a Dataverse Collection" vs. "Change
Collection Attributes" #10904
---
doc/sphinx-guides/source/api/native-api.rst | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst
index 6254742eebb..b464b6df393 100644
--- a/doc/sphinx-guides/source/api/native-api.rst
+++ b/doc/sphinx-guides/source/api/native-api.rst
@@ -126,6 +126,8 @@ Same as in :ref:`create-dataverse-api`, the request JSON supports an optional ``
To obtain an example of how these objects are included in the JSON file, download :download:`dataverse-complete-optional-params.json <../_static/api/dataverse-complete-optional-params.json>` file and modify it to suit your needs.
+See also :ref:`collection-attributes-api`.
+
.. _view-dataverse:
View a Dataverse Collection
@@ -1058,6 +1060,8 @@ The following attributes are supported:
* ``affiliation`` Affiliation
* ``filePIDsEnabled`` ("true" or "false") Restricted to use by superusers and only when the :ref:`:AllowEnablingFilePIDsPerCollection <:AllowEnablingFilePIDsPerCollection>` setting is true. Enables or disables registration of file-level PIDs in datasets within the collection (overriding the instance-wide setting).
+See also :ref:`update-dataverse-api`.
+
.. _collection-storage-quotas:
Update Collection Input Levels
From 6ea27e17ced9824a2b213880c81a04eaf7bb0c3d Mon Sep 17 00:00:00 2001
From: qqmyers
Date: Wed, 6 Nov 2024 10:41:23 -0500
Subject: [PATCH 098/270] Use normalized version of PID
Will use upper case form of DOI identifier to support case-insensitive
retrieval
---
.../edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java
index b203738a9fd..59b9f970f30 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java
@@ -313,6 +313,8 @@ public Dataset doImportHarvestedDataset(DataverseRequest dataverseRequest, Harve
// Creating a new dataset from scratch:
harvestedDataset = parser.parseDataset(obj);
+ //Use normalized form (e.g. upper case DOI)
+ harvestedDataset.setGlobalId(globalId);
harvestedDataset.setHarvestedFrom(harvestingClient);
harvestedDataset.setHarvestIdentifier(harvestIdentifier);
From fa0389d87992562822518878f2cb01065f483f76 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Wed, 6 Nov 2024 11:40:57 -0500
Subject: [PATCH 099/270] adding check for dataset order incorrect
---
doc/sphinx-guides/source/api/native-api.rst | 3 +++
src/main/java/edu/harvard/iq/dataverse/api/Datasets.java | 3 +++
src/main/java/propertyFiles/Bundle.properties | 1 +
src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java | 6 ++++++
4 files changed, 13 insertions(+)
diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst
index 5e4f9debe6b..371b1440d8d 100644
--- a/doc/sphinx-guides/source/api/native-api.rst
+++ b/doc/sphinx-guides/source/api/native-api.rst
@@ -1698,6 +1698,9 @@ Compare Versions of a Dataset
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Returns a list of fields that have changed between 2 Dataset versions within the Metadata and Terms of Access. Also includes the files that have been added or removed as well as files that have been modified.
+When compare includes an unpublished/draft version the api token must be associated with a user having view unpublished privileges
+An error will be returned if VERSION0 was not created before VERSION1
+
.. code-block:: bash
export SERVER_URL=https://demo.dataverse.org
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
index d7b0c78e611..d60f797f35c 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
@@ -3003,6 +3003,9 @@ public Response getCompareVersions(@Context ContainerRequestContext crc, @PathPa
DataverseRequest req = createDataverseRequest(getRequestUser(crc));
DatasetVersion dsv1 = getDatasetVersionOrDie(req, versionId1, findDatasetOrDie(id), uriInfo, headers);
DatasetVersion dsv2 = getDatasetVersionOrDie(req, versionId2, findDatasetOrDie(id), uriInfo, headers);
+ if (dsv1.getCreateTime().getTime() > dsv2.getCreateTime().getTime()) {
+ return error(BAD_REQUEST, BundleUtil.getStringFromBundle("dataset.version.compare.incorrect.order"));
+ }
return ok(DatasetVersion.compareVersions(dsv1, dsv2));
} catch (WrappedResponse wr) {
return wr.getResponse();
diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties
index 149e6a7e828..461de5c49de 100644
--- a/src/main/java/propertyFiles/Bundle.properties
+++ b/src/main/java/propertyFiles/Bundle.properties
@@ -2498,6 +2498,7 @@ dataset.version.file.changed=Files (Changed File Metadata: {0}
dataset.version.file.changed2=; Changed File Metadata: {0}
dataset.version.variablemetadata.changed=Variable Metadata (Changed Variable Metadata: {0}
dataset.version.variablemetadata.changed2=; Changed Variable Metadata: {0}
+dataset.version.compare.incorrect.order=Compare requires the older dataset version to be listed first.
#DataversePage.java
dataverse.item.required=Required
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
index 61977f6446c..9397b2246ec 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/DatasetsIT.java
@@ -5291,5 +5291,11 @@ public void testCompareDatasetVersionsAPI() throws InterruptedException {
.body("data.filesReplaced[0].newFile.fileName", CoreMatchers.equalTo("favicon-32x32.png"))
.body("data.TermsOfAccess", CoreMatchers.notNullValue())
.statusCode(OK.getStatusCode());
+
+ compareResponse = UtilIT.compareDatasetVersions(datasetPersistentId, ":draft", ":latest-published", apiToken);
+ compareResponse.prettyPrint();
+ compareResponse.then().assertThat()
+ .body("message", CoreMatchers.equalTo(BundleUtil.getStringFromBundle("dataset.version.compare.incorrect.order")))
+ .statusCode(BAD_REQUEST.getStatusCode());
}
}
From 91fef44c0080a5c505a6e170489762d4982e4f43 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Wed, 6 Nov 2024 11:44:43 -0500
Subject: [PATCH 100/270] adding check for dataset order incorrect
---
.../10888-add-api-for-comparing-dataset-versions.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/doc/release-notes/10888-add-api-for-comparing-dataset-versions.md b/doc/release-notes/10888-add-api-for-comparing-dataset-versions.md
index f9b3822d29d..b82441ee11a 100644
--- a/doc/release-notes/10888-add-api-for-comparing-dataset-versions.md
+++ b/doc/release-notes/10888-add-api-for-comparing-dataset-versions.md
@@ -3,6 +3,8 @@ The following API have been added:
/api/datasets/{persistentId}/versions/{versionId0}/compare/{versionId1}
This API lists the changes between 2 dataset versions. The Json response shows the changes per field within the Metadata block and the Terms Of Access. Also listed are the files that have been added or removed. Files that have been modified will also display the new file data plus the fields that have been modified.
+When compare includes an unpublished/draft version the api token must be associated with a user having view unpublished privileges
+An error will be returned if VERSION0 was not created before VERSION1
Example of Metadata Block field change:
```json
From b6df149b155cdfd37c27e3c5945cd201777f1303 Mon Sep 17 00:00:00 2001
From: ofahimIQSS
Date: Wed, 6 Nov 2024 11:48:57 -0500
Subject: [PATCH 101/270] Update making-releases.rst
---
doc/sphinx-guides/source/developers/making-releases.rst | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/doc/sphinx-guides/source/developers/making-releases.rst b/doc/sphinx-guides/source/developers/making-releases.rst
index 25297a23fca..888a56d7001 100755
--- a/doc/sphinx-guides/source/developers/making-releases.rst
+++ b/doc/sphinx-guides/source/developers/making-releases.rst
@@ -62,6 +62,11 @@ The task at or near release time is to collect these snippets into a single file
- Include instructions describing the steps required to upgrade the application from the previous version. These must be customized for release numbers and special circumstances such as changes to metadata blocks and infrastructure.
- Take the release notes .md through the regular Code Review and QA process. That is, make a pull request. Here's an example: https://github.com/IQSS/dataverse/pull/10866
+Upgrade Instructions for Internal
+---------------------------------
+To upgrade internal, go to /doc/release-notes, open the release-notes.md file for the current release and perform all the steps under "Upgrade Instructions".
+
+
Deploy Release Candidate to Demo
--------------------------------
From 0105fc0b91aa4cfe7e8a03894908d1e37184e819 Mon Sep 17 00:00:00 2001
From: ofahimIQSS
Date: Wed, 6 Nov 2024 11:55:18 -0500
Subject: [PATCH 102/270] Update making-releases.rst
---
doc/sphinx-guides/source/developers/making-releases.rst | 1 +
1 file changed, 1 insertion(+)
diff --git a/doc/sphinx-guides/source/developers/making-releases.rst b/doc/sphinx-guides/source/developers/making-releases.rst
index 888a56d7001..58589d9debc 100755
--- a/doc/sphinx-guides/source/developers/making-releases.rst
+++ b/doc/sphinx-guides/source/developers/making-releases.rst
@@ -64,6 +64,7 @@ The task at or near release time is to collect these snippets into a single file
Upgrade Instructions for Internal
---------------------------------
+
To upgrade internal, go to /doc/release-notes, open the release-notes.md file for the current release and perform all the steps under "Upgrade Instructions".
From 07e78c9207d5203c189c300a12fa7642260f157a Mon Sep 17 00:00:00 2001
From: ofahimIQSS
Date: Wed, 6 Nov 2024 11:55:51 -0500
Subject: [PATCH 103/270] Update making-releases.rst
---
doc/sphinx-guides/source/developers/making-releases.rst | 1 -
1 file changed, 1 deletion(-)
diff --git a/doc/sphinx-guides/source/developers/making-releases.rst b/doc/sphinx-guides/source/developers/making-releases.rst
index 58589d9debc..4b52b3ce922 100755
--- a/doc/sphinx-guides/source/developers/making-releases.rst
+++ b/doc/sphinx-guides/source/developers/making-releases.rst
@@ -67,7 +67,6 @@ Upgrade Instructions for Internal
To upgrade internal, go to /doc/release-notes, open the release-notes.md file for the current release and perform all the steps under "Upgrade Instructions".
-
Deploy Release Candidate to Demo
--------------------------------
From 3266d882a8700d272c501d3fde166a993c03caef Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Thu, 7 Nov 2024 09:47:00 -0500
Subject: [PATCH 104/270] Update doc/release-notes/8184-rename-private-url.md
Co-authored-by: Philip Durbin
---
doc/release-notes/8184-rename-private-url.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/doc/release-notes/8184-rename-private-url.md b/doc/release-notes/8184-rename-private-url.md
index 600e8dd228c..7c78080d4ea 100644
--- a/doc/release-notes/8184-rename-private-url.md
+++ b/doc/release-notes/8184-rename-private-url.md
@@ -4,4 +4,6 @@ With this release the name of the URL that may be used by dataset administrators
Also, additional information about the creation of Preview URLs has been added to the popup accessed via edit menu of the Dataset Page.
-Any Private URLs created in previous versions of Dataverse will continue to work as will the api for creation and deletion of Private URLs.
+Any Private URLs created in previous versions of Dataverse will continue to work.
+
+The old "privateUrl" API endpoints for the creation and deletion of Preview (formerly Private) URLs have been deprecated. They will continue to work but please switch to the "previewUrl" equivalents that have been [documented](https://dataverse-guide--10961.org.readthedocs.build/en/10961/api/native-api.html#create-a-preview-url-for-a-dataset) in the API Guide.
From d6001c92d7822dbf12d4645c56c6c289211c8c1c Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Thu, 7 Nov 2024 09:59:30 -0500
Subject: [PATCH 105/270] #8184 remove deprecated code redundancy
---
.../harvard/iq/dataverse/api/Datasets.java | 26 +++----------------
1 file changed, 4 insertions(+), 22 deletions(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
index 52610e10323..0ce06e204c3 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
@@ -2174,24 +2174,15 @@ public Response getAssignments(@Context ContainerRequestContext crc, @PathParam(
@Deprecated(forRemoval = true, since = "2024-10-17")
@Path("{id}/privateUrl")
public Response getPrivateUrlData(@Context ContainerRequestContext crc, @PathParam("id") String idSupplied) {
- return response( req -> {
- PrivateUrl privateUrl = execCommand(new GetPrivateUrlCommand(req, findDatasetOrDie(idSupplied)));
- return (privateUrl != null) ? ok(json(privateUrl))
- : error(Response.Status.NOT_FOUND, "Private URL not found.");
- }, getRequestUser(crc));
+ return getPreviewUrlData(crc, idSupplied);
}
@POST
@AuthRequired
@Deprecated(forRemoval = true, since = "2024-10-17")
@Path("{id}/privateUrl")
- public Response createPrivateUrl(@Context ContainerRequestContext crc, @PathParam("id") String idSupplied,@DefaultValue("false") @QueryParam ("anonymizedAccess") boolean anonymizedAccess) {
- if(anonymizedAccess && settingsSvc.getValueForKey(SettingsServiceBean.Key.AnonymizedFieldTypeNames)==null) {
- throw new NotAcceptableException("Anonymized Access not enabled");
- }
- return response(req ->
- ok(json(execCommand(
- new CreatePrivateUrlCommand(req, findDatasetOrDie(idSupplied), anonymizedAccess)))), getRequestUser(crc));
+ public Response createPrivateUrl(@Context ContainerRequestContext crc, @PathParam("id") String idSupplied, @DefaultValue("false") @QueryParam("anonymizedAccess") boolean anonymizedAccess) {
+ return createPreviewUrl(crc, idSupplied, anonymizedAccess);
}
@DELETE
@@ -2199,16 +2190,7 @@ public Response createPrivateUrl(@Context ContainerRequestContext crc, @PathPara
@Deprecated(forRemoval = true, since = "2024-10-17")
@Path("{id}/privateUrl")
public Response deletePrivateUrl(@Context ContainerRequestContext crc, @PathParam("id") String idSupplied) {
- return response( req -> {
- Dataset dataset = findDatasetOrDie(idSupplied);
- PrivateUrl privateUrl = execCommand(new GetPrivateUrlCommand(req, dataset));
- if (privateUrl != null) {
- execCommand(new DeletePrivateUrlCommand(req, dataset));
- return ok("Private URL deleted.");
- } else {
- return notFound("No Private URL to delete.");
- }
- }, getRequestUser(crc));
+ return deletePreviewUrl(crc, idSupplied);
}
@GET
From b64d44897011e3f61509688af79ec1d98928dd40 Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Thu, 7 Nov 2024 10:32:01 -0500
Subject: [PATCH 106/270] Update native-api.rst
---
doc/sphinx-guides/source/api/native-api.rst | 1 +
1 file changed, 1 insertion(+)
diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst
index 3cc5ac6c00b..d239d1cc0a1 100644
--- a/doc/sphinx-guides/source/api/native-api.rst
+++ b/doc/sphinx-guides/source/api/native-api.rst
@@ -2028,6 +2028,7 @@ Setting anonymizedAccess=true in your call will create a PreviewURL that only al
curl -H "X-Dataverse-key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X POST "https://demo.dataverse.org/api/datasets/24/previewUrl?anonymizedAccess=true"
+Note: Previous endpoints with privateUrl instead of previewUrl are deprecated, but supported.
Get the Preview URL for a Dataset
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
From db46d8abb6bf5b121fe08f2f08c62acf6859b007 Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Thu, 7 Nov 2024 10:51:03 -0500
Subject: [PATCH 107/270] Adding UPPER() to the named queries that search on
persistent identifiers, making the searches case-insensitive and making it
possible to look up any lower case or mixed-case persistent ids already in
the database. #11003
---
src/main/java/edu/harvard/iq/dataverse/DvObject.java | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/DvObject.java b/src/main/java/edu/harvard/iq/dataverse/DvObject.java
index a4882f772d6..30f45064582 100644
--- a/src/main/java/edu/harvard/iq/dataverse/DvObject.java
+++ b/src/main/java/edu/harvard/iq/dataverse/DvObject.java
@@ -27,9 +27,9 @@
@NamedQuery(name = "DvObject.ownedObjectsById",
query="SELECT COUNT(obj) FROM DvObject obj WHERE obj.owner.id=:id"),
@NamedQuery(name = "DvObject.findByGlobalId",
- query = "SELECT o FROM DvObject o WHERE o.identifier=:identifier and o.authority=:authority and o.protocol=:protocol and o.dtype=:dtype"),
+ query = "SELECT o FROM DvObject o WHERE UPPER(o.identifier)=:identifier and o.authority=:authority and o.protocol=:protocol and o.dtype=:dtype"),
@NamedQuery(name = "DvObject.findIdByGlobalId",
- query = "SELECT o.id FROM DvObject o WHERE o.identifier=:identifier and o.authority=:authority and o.protocol=:protocol and o.dtype=:dtype"),
+ query = "SELECT o.id FROM DvObject o WHERE UPPER(o.identifier)=:identifier and o.authority=:authority and o.protocol=:protocol and o.dtype=:dtype"),
@NamedQuery(name = "DvObject.findByAlternativeGlobalId",
query = "SELECT o FROM DvObject o, AlternativePersistentIdentifier a WHERE o.id = a.dvObject.id and a.identifier=:identifier and a.authority=:authority and a.protocol=:protocol and o.dtype=:dtype"),
@@ -37,7 +37,7 @@
query = "SELECT o.id FROM DvObject o, AlternativePersistentIdentifier a WHERE o.id = a.dvObject.id and a.identifier=:identifier and a.authority=:authority and a.protocol=:protocol and o.dtype=:dtype"),
@NamedQuery(name = "DvObject.findByProtocolIdentifierAuthority",
- query = "SELECT o FROM DvObject o WHERE o.identifier=:identifier and o.authority=:authority and o.protocol=:protocol"),
+ query = "SELECT o FROM DvObject o WHERE UPPER(o.identifier)=:identifier and o.authority=:authority and o.protocol=:protocol"),
@NamedQuery(name = "DvObject.findByOwnerId",
query = "SELECT o FROM DvObject o WHERE o.owner.id=:ownerId order by o.dtype desc, o.id"),
@NamedQuery(name = "DvObject.findByAuthenticatedUserId",
From 0accbc465f7ee446c042acf585d8a6d257335512 Mon Sep 17 00:00:00 2001
From: qqmyers
Date: Thu, 7 Nov 2024 12:27:11 -0500
Subject: [PATCH 108/270] tweak tests for case insensitivity, update docs/notes
for clarity
---
doc/sphinx-guides/source/installation/config.rst | 4 ++++
.../pidproviders/handle/HandlePidProvider.java | 5 +++++
.../pidproviders/perma/PermaLinkPidProvider.java | 3 +++
.../iq/dataverse/pidproviders/PidUtilTest.java | 15 +++++++++++----
4 files changed, 23 insertions(+), 4 deletions(-)
diff --git a/doc/sphinx-guides/source/installation/config.rst b/doc/sphinx-guides/source/installation/config.rst
index a2c27598b76..e3965e3cd7c 100644
--- a/doc/sphinx-guides/source/installation/config.rst
+++ b/doc/sphinx-guides/source/installation/config.rst
@@ -236,6 +236,10 @@ Dataverse automatically manages assigning PIDs and making them findable when dat
allow updating the PID target URLs and metadata of already-published datasets manually if needed `, e.g. if a Dataverse instance is
moved to a new URL or when the software is updated to generate additional metadata or address schema changes at the PID service.
+Note that while some forms of PIDs (Handles, PermaLinks) are technically case sensitive, common practice is to avoid creating PIDs that differ only by case.
+Dataverse treats PIDs of all types as case-insensitive (as DOIs are by definition). This means that Dataverse will find datasets (in search, to display dataset pages, etc.)
+when the PIDs entered do not match the case of the original but will have a problem if two PIDs that differ only by case exist in one instance.
+
Testing PID Providers
+++++++++++++++++++++
diff --git a/src/main/java/edu/harvard/iq/dataverse/pidproviders/handle/HandlePidProvider.java b/src/main/java/edu/harvard/iq/dataverse/pidproviders/handle/HandlePidProvider.java
index 9d61663d034..1f03d8a6cfb 100644
--- a/src/main/java/edu/harvard/iq/dataverse/pidproviders/handle/HandlePidProvider.java
+++ b/src/main/java/edu/harvard/iq/dataverse/pidproviders/handle/HandlePidProvider.java
@@ -59,6 +59,11 @@
* service.
* As of now, it only does the registration updates, to accommodate
* the modifyRegistration datasets API sub-command.
+ *
+ * Note that while Handles are nominally case sensitive, handle.net is
+ * configured to be case-insensitive and Dataverse makes case-insensitve
+ * database look-ups to find Handles (See #11003). That said, database
+ * entries are stored in the case matching the configuration of the provider.
*/
public class HandlePidProvider extends AbstractPidProvider {
diff --git a/src/main/java/edu/harvard/iq/dataverse/pidproviders/perma/PermaLinkPidProvider.java b/src/main/java/edu/harvard/iq/dataverse/pidproviders/perma/PermaLinkPidProvider.java
index 7b55292350f..2cc0d41ede7 100644
--- a/src/main/java/edu/harvard/iq/dataverse/pidproviders/perma/PermaLinkPidProvider.java
+++ b/src/main/java/edu/harvard/iq/dataverse/pidproviders/perma/PermaLinkPidProvider.java
@@ -24,6 +24,9 @@
* overridable by a configurable parameter to support use of an external
* resolver.
*
+ * Note that while PermaLinks are nominally case sensitive, Dataverse makes
+ * case-insensitve database look-ups to find them (See #11003). That said, database
+ * entries are stored in the case matching the configuration of the provider.
*/
public class PermaLinkPidProvider extends AbstractPidProvider {
diff --git a/src/test/java/edu/harvard/iq/dataverse/pidproviders/PidUtilTest.java b/src/test/java/edu/harvard/iq/dataverse/pidproviders/PidUtilTest.java
index ecf18e6b1ca..bacb231b4d5 100644
--- a/src/test/java/edu/harvard/iq/dataverse/pidproviders/PidUtilTest.java
+++ b/src/test/java/edu/harvard/iq/dataverse/pidproviders/PidUtilTest.java
@@ -99,7 +99,7 @@
@JvmSetting(key = JvmSettings.PID_PROVIDER_LABEL, value = "FAKE 1", varArgs = "fake1")
@JvmSetting(key = JvmSettings.PID_PROVIDER_TYPE, value = FakeDOIProvider.TYPE, varArgs = "fake1")
@JvmSetting(key = JvmSettings.PID_PROVIDER_AUTHORITY, value = "10.5074", varArgs = "fake1")
-@JvmSetting(key = JvmSettings.PID_PROVIDER_SHOULDER, value = "FK", varArgs = "fake1")
+@JvmSetting(key = JvmSettings.PID_PROVIDER_SHOULDER, value = "fk", varArgs = "fake1")
@JvmSetting(key = JvmSettings.PID_PROVIDER_MANAGED_LIST, value = "doi:10.5073/FK3ABCDEF", varArgs ="fake1")
//HANDLE 1
@@ -315,6 +315,13 @@ public void testUnmanagedParsing() throws IOException {
GlobalId pid6 = PidUtil.parseAsGlobalID(pid6String);
assertEquals(pid6String, pid6.asString());
assertEquals(UnmanagedPermaLinkPidProvider.ID, pid6.getProviderId());
+
+ //Lowercase test for unmanaged DOIs
+ String pid7String = "doi:10.5281/zenodo.6381129";
+ GlobalId pid7 = PidUtil.parseAsGlobalID(pid7String);
+ assertEquals(UnmanagedDOIProvider.ID, pid5.getProviderId());
+ assertEquals(pid7String.toUpperCase().replace("DOI", "doi"), pid7.asString());
+
}
@@ -353,15 +360,15 @@ public void testExcludedSetParsing() throws IOException {
@Test
public void testManagedSetParsing() throws IOException {
- String pid1String = "doi:10.5073/FK3ABCDEF";
+ String pid1String = "doi:10.5073/fk3ABCDEF";
GlobalId pid2 = PidUtil.parseAsGlobalID(pid1String);
- assertEquals(pid1String, pid2.asString());
+ assertEquals(pid1String.toUpperCase().replace("DOI", "doi"), pid2.asString());
assertEquals("fake1", pid2.getProviderId());
assertEquals("https://doi.org/" + pid2.getAuthority() + PidUtil.getPidProvider(pid2.getProviderId()).getSeparator() + pid2.getIdentifier(),pid2.asURL());
assertEquals("10.5073", pid2.getAuthority());
assertEquals(AbstractDOIProvider.DOI_PROTOCOL, pid2.getProtocol());
GlobalId pid3 = PidUtil.parseAsGlobalID(pid2.asURL());
- assertEquals(pid1String, pid3.asString());
+ assertEquals(pid1String.toUpperCase().replace("DOI", "doi"), pid3.asString());
assertEquals("fake1", pid3.getProviderId());
assertFalse(PidUtil.getPidProvider(pid3.getProviderId()).canCreatePidsLike(pid3));
From 4c05bce9c1277bba361a374e73aa5a752ae302f0 Mon Sep 17 00:00:00 2001
From: qqmyers
Date: Thu, 7 Nov 2024 12:53:07 -0500
Subject: [PATCH 109/270] store in original form so we can use it if the source
is case-sensitive
---
.../harvard/iq/dataverse/api/imports/ImportServiceBean.java | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java
index 59b9f970f30..ee4609a7c56 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java
@@ -313,9 +313,7 @@ public Dataset doImportHarvestedDataset(DataverseRequest dataverseRequest, Harve
// Creating a new dataset from scratch:
harvestedDataset = parser.parseDataset(obj);
- //Use normalized form (e.g. upper case DOI)
- harvestedDataset.setGlobalId(globalId);
-
+
harvestedDataset.setHarvestedFrom(harvestingClient);
harvestedDataset.setHarvestIdentifier(harvestIdentifier);
From 73d17a225056d83011285e2c4ecf3cec879814e7 Mon Sep 17 00:00:00 2001
From: qqmyers
Date: Thu, 7 Nov 2024 14:22:56 -0500
Subject: [PATCH 110/270] add UPPER for both sides of comparison
---
src/main/java/edu/harvard/iq/dataverse/DvObject.java | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/DvObject.java b/src/main/java/edu/harvard/iq/dataverse/DvObject.java
index 30f45064582..cc874937632 100644
--- a/src/main/java/edu/harvard/iq/dataverse/DvObject.java
+++ b/src/main/java/edu/harvard/iq/dataverse/DvObject.java
@@ -27,9 +27,9 @@
@NamedQuery(name = "DvObject.ownedObjectsById",
query="SELECT COUNT(obj) FROM DvObject obj WHERE obj.owner.id=:id"),
@NamedQuery(name = "DvObject.findByGlobalId",
- query = "SELECT o FROM DvObject o WHERE UPPER(o.identifier)=:identifier and o.authority=:authority and o.protocol=:protocol and o.dtype=:dtype"),
+ query = "SELECT o FROM DvObject o WHERE UPPER(o.identifier)=UPPER(:identifier) and o.authority=:authority and o.protocol=:protocol and o.dtype=:dtype"),
@NamedQuery(name = "DvObject.findIdByGlobalId",
- query = "SELECT o.id FROM DvObject o WHERE UPPER(o.identifier)=:identifier and o.authority=:authority and o.protocol=:protocol and o.dtype=:dtype"),
+ query = "SELECT o.id FROM DvObject o WHERE UPPER(o.identifier)=UPPER(:identifier) and o.authority=:authority and o.protocol=:protocol and o.dtype=:dtype"),
@NamedQuery(name = "DvObject.findByAlternativeGlobalId",
query = "SELECT o FROM DvObject o, AlternativePersistentIdentifier a WHERE o.id = a.dvObject.id and a.identifier=:identifier and a.authority=:authority and a.protocol=:protocol and o.dtype=:dtype"),
@@ -37,7 +37,7 @@
query = "SELECT o.id FROM DvObject o, AlternativePersistentIdentifier a WHERE o.id = a.dvObject.id and a.identifier=:identifier and a.authority=:authority and a.protocol=:protocol and o.dtype=:dtype"),
@NamedQuery(name = "DvObject.findByProtocolIdentifierAuthority",
- query = "SELECT o FROM DvObject o WHERE UPPER(o.identifier)=:identifier and o.authority=:authority and o.protocol=:protocol"),
+ query = "SELECT o FROM DvObject o WHERE UPPER(o.identifier)=UPPER(:identifier) and o.authority=:authority and o.protocol=:protocol"),
@NamedQuery(name = "DvObject.findByOwnerId",
query = "SELECT o FROM DvObject o WHERE o.owner.id=:ownerId order by o.dtype desc, o.id"),
@NamedQuery(name = "DvObject.findByAuthenticatedUserId",
From dae9287cfdfef6f153fc7c984692ea1167fdf805 Mon Sep 17 00:00:00 2001
From: qqmyers
Date: Thu, 7 Nov 2024 15:58:31 -0500
Subject: [PATCH 111/270] Add a new index with upper(identifier)
---
src/main/java/edu/harvard/iq/dataverse/DvObject.java | 3 ++-
src/main/resources/db/migration/V6.4.0.1.sql | 4 ++++
2 files changed, 6 insertions(+), 1 deletion(-)
create mode 100644 src/main/resources/db/migration/V6.4.0.1.sql
diff --git a/src/main/java/edu/harvard/iq/dataverse/DvObject.java b/src/main/java/edu/harvard/iq/dataverse/DvObject.java
index cc874937632..5dab43fbdbd 100644
--- a/src/main/java/edu/harvard/iq/dataverse/DvObject.java
+++ b/src/main/java/edu/harvard/iq/dataverse/DvObject.java
@@ -53,7 +53,8 @@
@Table(indexes = {@Index(columnList="dtype")
, @Index(columnList="owner_id")
, @Index(columnList="creator_id")
- , @Index(columnList="releaseuser_id")},
+ , @Index(columnList="releaseuser_id")
+ , @Index(columnList="authority,protocol, UPPER(identifier)", name="INDEX_DVOBJECT_authority_protocol_upper_identifier")},
uniqueConstraints = {@UniqueConstraint(columnNames = {"authority,protocol,identifier"}),@UniqueConstraint(columnNames = {"owner_id,storageidentifier"})})
public abstract class DvObject extends DataverseEntity implements java.io.Serializable {
diff --git a/src/main/resources/db/migration/V6.4.0.1.sql b/src/main/resources/db/migration/V6.4.0.1.sql
new file mode 100644
index 00000000000..0bcd87dd736
--- /dev/null
+++ b/src/main/resources/db/migration/V6.4.0.1.sql
@@ -0,0 +1,4 @@
+-- Adding a case-insensitive index related to #11003
+--
+
+CREATE UNIQUE INDEX IF NOT EXISTS INDEX_DVOBJECT_authority_protocol_upper_identifier ON dvobject (authority, protocol, UPPER(identifier));
\ No newline at end of file
From b8c0c405984ea36c1bc7dfb9b54ff6411ffd2dc2 Mon Sep 17 00:00:00 2001
From: qqmyers
Date: Thu, 7 Nov 2024 16:01:22 -0500
Subject: [PATCH 112/270] tweak prior release note
---
.../10708 - MDC Citation and DOI parsing improvements.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/release-notes/10708 - MDC Citation and DOI parsing improvements.md b/doc/release-notes/10708 - MDC Citation and DOI parsing improvements.md
index 1dcd293df77..86c1bb14d32 100644
--- a/doc/release-notes/10708 - MDC Citation and DOI parsing improvements.md
+++ b/doc/release-notes/10708 - MDC Citation and DOI parsing improvements.md
@@ -1,3 +1,3 @@
MDC Citation retrieval with the PID settings has been fixed.
-DOI parsing in Dataverse is case insensitive, improving interaction with services that may change the case.
+PID parsing in Dataverse is now case insensitive, improving interaction with services that may change the case of PIDs.
Warnings related to managed/excluded PID lists for PID providers have been reduced
From 0688783d39e58724820cce1c69019271f0129900 Mon Sep 17 00:00:00 2001
From: GPortas
Date: Tue, 12 Nov 2024 12:18:34 +0000
Subject: [PATCH 113/270] Added: isMetadataBlockRoot and isFacetRoot to
getDataverse json response
---
.../edu/harvard/iq/dataverse/util/json/JsonPrinter.java | 4 +++-
.../java/edu/harvard/iq/dataverse/api/DataversesIT.java | 6 ++++--
2 files changed, 7 insertions(+), 3 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 1bdee48b14d..f884d313d64 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
@@ -276,7 +276,9 @@ public static JsonObjectBuilder json(Dataverse dv, Boolean hideEmail, Boolean re
}
bld.add("permissionRoot", dv.isPermissionRoot())
.add("description", dv.getDescription())
- .add("dataverseType", dv.getDataverseType().name());
+ .add("dataverseType", dv.getDataverseType().name())
+ .add("isMetadataBlockRoot", dv.isMetadataBlockRoot())
+ .add("isFacetRoot", dv.isFacetRoot());
if (dv.getOwner() != null) {
bld.add("ownerId", dv.getOwner().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 01c02900158..9567cf3910a 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java
@@ -135,14 +135,16 @@ public void testDataverseCategory() {
public void testMinimalDataverse() throws FileNotFoundException {
Response createUser = UtilIT.createRandomUser();
createUser.prettyPrint();
- String username = UtilIT.getUsernameFromResponse(createUser);
String apiToken = UtilIT.getApiTokenFromResponse(createUser);
JsonObject dvJson;
FileReader reader = new FileReader("doc/sphinx-guides/source/_static/api/dataverse-minimal.json");
dvJson = Json.createReader(reader).readObject();
Response create = UtilIT.createDataverse(dvJson, apiToken);
create.prettyPrint();
- create.then().assertThat().statusCode(CREATED.getStatusCode());
+ create.then().assertThat()
+ .body("data.isMetadataBlockRoot", equalTo(false))
+ .body("data.isFacetRoot", equalTo(false))
+ .statusCode(CREATED.getStatusCode());
Response deleteDataverse = UtilIT.deleteDataverse("science", apiToken);
deleteDataverse.prettyPrint();
deleteDataverse.then().assertThat().statusCode(OK.getStatusCode());
From 5198a9f47b80e09f0a3f22a4a0af1279b679a2d0 Mon Sep 17 00:00:00 2001
From: GPortas
Date: Tue, 12 Nov 2024 12:20:59 +0000
Subject: [PATCH 114/270] Added: release notes for #11012
---
doc/release-notes/11012-get-dataverse-api-ext.md | 1 +
1 file changed, 1 insertion(+)
create mode 100644 doc/release-notes/11012-get-dataverse-api-ext.md
diff --git a/doc/release-notes/11012-get-dataverse-api-ext.md b/doc/release-notes/11012-get-dataverse-api-ext.md
new file mode 100644
index 00000000000..641aa373174
--- /dev/null
+++ b/doc/release-notes/11012-get-dataverse-api-ext.md
@@ -0,0 +1 @@
+The JSON payload of the getDataverse endpoint has been extended to include properties isMetadataBlockRoot and isFacetRoot.
From 373f0f7a79716106b42f76430e20b62487f320bb Mon Sep 17 00:00:00 2001
From: Philip Durbin
Date: Tue, 12 Nov 2024 12:13:41 -0500
Subject: [PATCH 115/270] link to issues and PR in release note
---
doc/release-notes/8184-rename-private-url.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/doc/release-notes/8184-rename-private-url.md b/doc/release-notes/8184-rename-private-url.md
index 7c78080d4ea..7acb03fd735 100644
--- a/doc/release-notes/8184-rename-private-url.md
+++ b/doc/release-notes/8184-rename-private-url.md
@@ -7,3 +7,5 @@ Also, additional information about the creation of Preview URLs has been added t
Any Private URLs created in previous versions of Dataverse will continue to work.
The old "privateUrl" API endpoints for the creation and deletion of Preview (formerly Private) URLs have been deprecated. They will continue to work but please switch to the "previewUrl" equivalents that have been [documented](https://dataverse-guide--10961.org.readthedocs.build/en/10961/api/native-api.html#create-a-preview-url-for-a-dataset) in the API Guide.
+
+See also #8184, #8185, #10950, and #10961.
From 9367eb1e8be85f91e0e0b015a084084671cb4b0e Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Tue, 12 Nov 2024 14:17:59 -0500
Subject: [PATCH 116/270] add comment to yml files as to reason for v4.1.7
---
.github/workflows/deploy_beta_testing.yml | 1 +
.github/workflows/maven_unit_test.yml | 2 ++
2 files changed, 3 insertions(+)
diff --git a/.github/workflows/deploy_beta_testing.yml b/.github/workflows/deploy_beta_testing.yml
index c36a4dfab43..2c684aa8ad1 100644
--- a/.github/workflows/deploy_beta_testing.yml
+++ b/.github/workflows/deploy_beta_testing.yml
@@ -45,6 +45,7 @@ jobs:
- uses: actions/checkout@v4
- name: Download war artifact
+ # using v4.1.7 due to a bug in v4
uses: actions/download-artifact@v4.1.7
with:
name: built-app
diff --git a/.github/workflows/maven_unit_test.yml b/.github/workflows/maven_unit_test.yml
index 18a741e5ca5..2ce872cd55c 100644
--- a/.github/workflows/maven_unit_test.yml
+++ b/.github/workflows/maven_unit_test.yml
@@ -107,6 +107,7 @@ jobs:
cache: maven
# Get the build output from the unit test job
+ # using v4.1.7 due to a bug in v4
- uses: actions/download-artifact@v4.1.7
with:
name: java-artifacts
@@ -140,6 +141,7 @@ jobs:
cache: maven
# Get the build output from the integration test job
+ # using v4.1.7 due to a bug in v4
- uses: actions/download-artifact@v4.1.7
with:
name: java-reportdir
From 594acb7c0cdaa3815f0fc3861d97022e0df5a66e Mon Sep 17 00:00:00 2001
From: Philip Durbin
Date: Tue, 12 Nov 2024 14:29:58 -0500
Subject: [PATCH 117/270] remove Gson and test based on content, not string
match #10739
---
.../edu/harvard/iq/dataverse/api/InfoIT.java | 21 +++--
src/test/resources/json/export-formats.json | 84 ++++++++++++++++++-
2 files changed, 95 insertions(+), 10 deletions(-)
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/InfoIT.java b/src/test/java/edu/harvard/iq/dataverse/api/InfoIT.java
index 286f9789ed6..b198d2769a0 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/InfoIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/InfoIT.java
@@ -3,17 +3,17 @@
import static io.restassured.RestAssured.given;
import io.restassured.response.Response;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
-import com.google.gson.Gson;
-import com.google.gson.JsonObject;
import org.junit.jupiter.api.AfterAll;
-import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
-import static jakarta.ws.rs.core.Response.Status.BAD_REQUEST;
import static jakarta.ws.rs.core.Response.Status.NOT_FOUND;
import static jakarta.ws.rs.core.Response.Status.OK;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Paths;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.notNullValue;
+import org.skyscreamer.jsonassert.JSONAssert;
public class InfoIT {
@@ -85,16 +85,19 @@ public void testGetZipDownloadLimit() {
}
@Test
- public void testGetExportFormats() {
+ public void testGetExportFormats() throws IOException {
Response response = given().urlEncodingEnabled(false)
.get("/api/info/exportFormats");
response.prettyPrint();
response.then().assertThat().statusCode(OK.getStatusCode());
- String expectedJson = UtilIT.getDatasetJson("src/test/resources/json/export-formats.json");
- JsonObject expectedJsonObject = new Gson().fromJson(expectedJson, JsonObject.class);
- JsonObject actualJsonObject = new Gson().fromJson(response.getBody().asString(), JsonObject.class);
- assertEquals(expectedJsonObject, actualJsonObject.get("data"));
+ String actual = response.getBody().asString();
+ String expected =
+ java.nio.file.Files.readString(
+ Paths.get("src/test/resources/json/export-formats.json"),
+ StandardCharsets.UTF_8);
+ JSONAssert.assertEquals(expected, actual, true);
+
}
diff --git a/src/test/resources/json/export-formats.json b/src/test/resources/json/export-formats.json
index 9cf1984eb6a..0bca2314a8e 100644
--- a/src/test/resources/json/export-formats.json
+++ b/src/test/resources/json/export-formats.json
@@ -1 +1,83 @@
-{"OAI_ORE":{"displayName":"OAI_ORE","mediaType":"application/json","isHarvestable":false,"isVisibleInUserInterface":true},"Datacite":{"displayName":"DataCite","mediaType":"application/xml","isHarvestable":true,"isVisibleInUserInterface":true,"XMLNameSpace":"http://datacite.org/schema/kernel-3","XMLSchemaLocation":"http://datacite.org/schema/kernel-3 http://schema.datacite.org/meta/kernel-3/metadata.xsd","XMLSchemaVersion":"3.0"},"oai_dc":{"displayName":"Dublin Core","mediaType":"application/xml","isHarvestable":true,"isVisibleInUserInterface":false,"XMLNameSpace":"http://www.openarchives.org/OAI/2.0/oai_dc/","XMLSchemaLocation":"http://www.openarchives.org/OAI/2.0/oai_dc.xsd","XMLSchemaVersion":"2.0"},"oai_datacite":{"displayName":"OpenAIRE","mediaType":"application/xml","isHarvestable":true,"isVisibleInUserInterface":true,"XMLNameSpace":"http://datacite.org/schema/kernel-4","XMLSchemaLocation":"http://schema.datacite.org/meta/kernel-4.1/metadata.xsd","XMLSchemaVersion":"4.1"},"schema.org":{"displayName":"Schema.org JSON-LD","mediaType":"application/json","isHarvestable":false,"isVisibleInUserInterface":true},"ddi":{"displayName":"DDI","mediaType":"application/xml","isHarvestable":false,"isVisibleInUserInterface":true,"XMLNameSpace":"ddi:codebook:2_5","XMLSchemaLocation":"https://ddialliance.org/Specification/DDI-Codebook/2.5/XMLSchema/codebook.xsd","XMLSchemaVersion":"2.5"},"dcterms":{"displayName":"Dublin Core","mediaType":"application/xml","isHarvestable":false,"isVisibleInUserInterface":true,"XMLNameSpace":"http://purl.org/dc/terms/","XMLSchemaLocation":"http://dublincore.org/schemas/xmls/qdc/dcterms.xsd","XMLSchemaVersion":"2.0"},"html":{"displayName":"DDI HTML Codebook","mediaType":"text/html","isHarvestable":false,"isVisibleInUserInterface":true},"dataverse_json":{"displayName":"JSON","mediaType":"application/json","isHarvestable":true,"isVisibleInUserInterface":true},"oai_ddi":{"displayName":"DDI","mediaType":"application/xml","isHarvestable":true,"isVisibleInUserInterface":false,"XMLNameSpace":"ddi:codebook:2_5","XMLSchemaLocation":"https://ddialliance.org/Specification/DDI-Codebook/2.5/XMLSchema/codebook.xsd","XMLSchemaVersion":"2.5"}}
\ No newline at end of file
+{
+ "status": "OK",
+ "data": {
+ "OAI_ORE": {
+ "displayName": "OAI_ORE",
+ "mediaType": "application/json",
+ "isHarvestable": false,
+ "isVisibleInUserInterface": true
+ },
+ "Datacite": {
+ "displayName": "DataCite",
+ "mediaType": "application/xml",
+ "isHarvestable": true,
+ "isVisibleInUserInterface": true,
+ "XMLNameSpace": "http://datacite.org/schema/kernel-3",
+ "XMLSchemaLocation": "http://datacite.org/schema/kernel-3 http://schema.datacite.org/meta/kernel-3/metadata.xsd",
+ "XMLSchemaVersion": "3.0"
+ },
+ "oai_dc": {
+ "displayName": "Dublin Core",
+ "mediaType": "application/xml",
+ "isHarvestable": true,
+ "isVisibleInUserInterface": false,
+ "XMLNameSpace": "http://www.openarchives.org/OAI/2.0/oai_dc/",
+ "XMLSchemaLocation": "http://www.openarchives.org/OAI/2.0/oai_dc.xsd",
+ "XMLSchemaVersion": "2.0"
+ },
+ "oai_datacite": {
+ "displayName": "OpenAIRE",
+ "mediaType": "application/xml",
+ "isHarvestable": true,
+ "isVisibleInUserInterface": true,
+ "XMLNameSpace": "http://datacite.org/schema/kernel-4",
+ "XMLSchemaLocation": "http://schema.datacite.org/meta/kernel-4.1/metadata.xsd",
+ "XMLSchemaVersion": "4.1"
+ },
+ "schema.org": {
+ "displayName": "Schema.org JSON-LD",
+ "mediaType": "application/json",
+ "isHarvestable": false,
+ "isVisibleInUserInterface": true
+ },
+ "ddi": {
+ "displayName": "DDI",
+ "mediaType": "application/xml",
+ "isHarvestable": false,
+ "isVisibleInUserInterface": true,
+ "XMLNameSpace": "ddi:codebook:2_5",
+ "XMLSchemaLocation": "https://ddialliance.org/Specification/DDI-Codebook/2.5/XMLSchema/codebook.xsd",
+ "XMLSchemaVersion": "2.5"
+ },
+ "dcterms": {
+ "displayName": "Dublin Core",
+ "mediaType": "application/xml",
+ "isHarvestable": false,
+ "isVisibleInUserInterface": true,
+ "XMLNameSpace": "http://purl.org/dc/terms/",
+ "XMLSchemaLocation": "http://dublincore.org/schemas/xmls/qdc/dcterms.xsd",
+ "XMLSchemaVersion": "2.0"
+ },
+ "html": {
+ "displayName": "DDI HTML Codebook",
+ "mediaType": "text/html",
+ "isHarvestable": false,
+ "isVisibleInUserInterface": true
+ },
+ "dataverse_json": {
+ "displayName": "JSON",
+ "mediaType": "application/json",
+ "isHarvestable": true,
+ "isVisibleInUserInterface": true
+ },
+ "oai_ddi": {
+ "displayName": "DDI",
+ "mediaType": "application/xml",
+ "isHarvestable": true,
+ "isVisibleInUserInterface": false,
+ "XMLNameSpace": "ddi:codebook:2_5",
+ "XMLSchemaLocation": "https://ddialliance.org/Specification/DDI-Codebook/2.5/XMLSchema/codebook.xsd",
+ "XMLSchemaVersion": "2.5"
+ }
+ }
+}
From 60d6f92c6985f0b489a8f56dace2ad3d8b1628e5 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Wed, 13 Nov 2024 09:36:17 -0500
Subject: [PATCH 118/270] audit physical files
---
.../220-harvard-edu-audit-files.md | 16 ++
doc/sphinx-guides/source/api/native-api.rst | 55 ++++++
.../edu/harvard/iq/dataverse/api/Admin.java | 161 +++++++++++++++---
.../iq/dataverse/dataaccess/S3AccessIO.java | 6 +
.../edu/harvard/iq/dataverse/api/AdminIT.java | 49 +++++-
.../edu/harvard/iq/dataverse/api/UtilIT.java | 16 ++
6 files changed, 275 insertions(+), 28 deletions(-)
create mode 100644 doc/release-notes/220-harvard-edu-audit-files.md
diff --git a/doc/release-notes/220-harvard-edu-audit-files.md b/doc/release-notes/220-harvard-edu-audit-files.md
new file mode 100644
index 00000000000..536554313cf
--- /dev/null
+++ b/doc/release-notes/220-harvard-edu-audit-files.md
@@ -0,0 +1,16 @@
+### New API to Audit Datafiles across the database
+
+This is a superuser only tool to audit Datasets with DataFiles where the physical files are missing or the file metadata is missing.
+The Datasets scanned can be limited by optional firstId and lastId query parameters, or a given CSV list of Dataset Identifiers.
+Once the audit report is generated, an Administrator can either delete the missing file(s) from the Dataset or contact the author to re-upload the missing file(s).
+
+The Json response includes:
+- List of files in each DataFile where the file exists in the database but the physical file is not on the file store.
+- List of DataFiles where the FileMetadata is missing.
+- Other failures found when trying to process the Datasets
+
+curl "http://localhost:8080/api/admin/datafiles/auditFiles
+curl "http://localhost:8080/api/admin/datafiles/auditFiles?firstId=0&lastId=1000"
+curl "http://localhost:8080/api/admin/datafiles/auditFiles?DatasetIdentifierList=doi:10.5072/FK2/RVNT9Q,doi:10.5072/FK2/RVNT9Q
+
+For more information, see issue [#220](https://github.com/IQSS/dataverse.harvard.edu/issues/220)
diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst
index 6254742eebb..6fc10bdfa08 100644
--- a/doc/sphinx-guides/source/api/native-api.rst
+++ b/doc/sphinx-guides/source/api/native-api.rst
@@ -6200,6 +6200,61 @@ Note that if you are attempting to validate a very large number of datasets in y
asadmin set server-config.network-config.protocols.protocol.http-listener-1.http.request-timeout-seconds=3600
+Datafile Audit
+~~~~~~~~~~~~~~
+
+Produce an Audit report of missing files and FileMetadata for Datasets.
+Scans the Datasets in the database and verifies that the stored files exist. If the files are missing or if the FileMetadata is missing this information is returned in a Json response::
+
+ curl "$SERVER_URL/api/admin/datafiles/auditFiles"
+
+Optional Parameters are available for filtering the Datasets scanned.
+
+For auditing the Datasets in a paged manor (firstId and lastId)::
+
+ curl "$SERVER_URL/api/admin/datafiles/auditFiles?firstId=0&lastId=1000"
+
+Auditing specific Datasets (comma separated list)::
+
+ curl "$SERVER_URL/api/admin/datafiles/auditFiles?DatasetIdentifierList=doi.org/10.5072/FK2/JXYBJS,doi.org/10.7910/DVN/MPU019
+
+Sample Json Audit Response::
+
+ {
+ "status": "OK",
+ "data": {
+ "firstId": 0,
+ "lastId": 100,
+ "DatasetIdentifierList": [
+ "doi.org/10.5072/FK2/XXXXXX",
+ "doi.org/10.5072/FK2/JXYBJS",
+ "doi.org/10.7910/DVN/MPU019"
+ ],
+ "datasetsChecked": 100,
+ "datasets": [
+ {
+ "id": 6,
+ "identifier": "FK2/JXYBJS",
+ "persistentURL": "https://doi.org/10.5072/FK2/JXYBJS",
+ "missingFileMetadata": [
+ "local://1930cce4f2d-855ccc51fcbb, DataFile Id:7"
+ ]
+ },
+ {
+ "id": 47731,
+ "identifier": "DVN/MPU019",
+ "persistentURL": "https://doi.org/10.7910/DVN/MPU019",
+ "missingFiles": [
+ "s3://dvn-cloud:298910, jihad_metadata_edited.csv"
+ ]
+ }
+ ],
+ "failures": [
+ "DatasetIdentifier Not Found: doi.org/10.5072/FK2/XXXXXX"
+ ]
+ }
+ }
+
Workflows
~~~~~~~~~
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java
index 54e5eaf7b84..ecd9b71cc8d 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java
@@ -1,28 +1,11 @@
package edu.harvard.iq.dataverse.api;
-import edu.harvard.iq.dataverse.BannerMessage;
-import edu.harvard.iq.dataverse.BannerMessageServiceBean;
-import edu.harvard.iq.dataverse.BannerMessageText;
-import edu.harvard.iq.dataverse.DataFile;
-import edu.harvard.iq.dataverse.DataFileServiceBean;
-import edu.harvard.iq.dataverse.Dataset;
-import edu.harvard.iq.dataverse.DatasetServiceBean;
-import edu.harvard.iq.dataverse.DatasetVersion;
-import edu.harvard.iq.dataverse.DatasetVersionServiceBean;
-import edu.harvard.iq.dataverse.Dataverse;
-import edu.harvard.iq.dataverse.DataverseRequestServiceBean;
-import edu.harvard.iq.dataverse.DataverseServiceBean;
-import edu.harvard.iq.dataverse.DataverseSession;
-import edu.harvard.iq.dataverse.DvObject;
-import edu.harvard.iq.dataverse.DvObjectServiceBean;
+import edu.harvard.iq.dataverse.*;
import edu.harvard.iq.dataverse.api.auth.AuthRequired;
import edu.harvard.iq.dataverse.settings.JvmSettings;
import edu.harvard.iq.dataverse.util.StringUtil;
+import edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder;
import edu.harvard.iq.dataverse.validation.EMailValidator;
-import edu.harvard.iq.dataverse.EjbDataverseEngine;
-import edu.harvard.iq.dataverse.Template;
-import edu.harvard.iq.dataverse.TemplateServiceBean;
-import edu.harvard.iq.dataverse.UserServiceBean;
import edu.harvard.iq.dataverse.actionlogging.ActionLogRecord;
import edu.harvard.iq.dataverse.api.dto.RoleDTO;
import edu.harvard.iq.dataverse.authorization.AuthenticatedUserDisplayInfo;
@@ -66,8 +49,9 @@
import java.io.InputStream;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
-import java.util.Map;
+import java.util.*;
import java.util.Map.Entry;
+import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import jakarta.ejb.EJB;
@@ -81,7 +65,6 @@
import org.apache.commons.io.IOUtils;
-import java.util.List;
import edu.harvard.iq.dataverse.authorization.AuthTestDataServiceBean;
import edu.harvard.iq.dataverse.authorization.AuthenticationProvidersRegistrationServiceBean;
import edu.harvard.iq.dataverse.authorization.DataverseRole;
@@ -118,9 +101,7 @@
import static edu.harvard.iq.dataverse.util.json.JsonPrinter.json;
import static edu.harvard.iq.dataverse.util.json.JsonPrinter.rolesToJson;
import static edu.harvard.iq.dataverse.util.json.JsonPrinter.toJsonArray;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
+
import jakarta.inject.Inject;
import jakarta.json.JsonArray;
import jakarta.persistence.Query;
@@ -128,7 +109,6 @@
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.StreamingOutput;
import java.nio.file.Paths;
-import java.util.TreeMap;
/**
* Where the secure, setup API calls live.
@@ -2541,4 +2521,135 @@ public Response getFeatureFlag(@PathParam("flag") String flagIn) {
}
}
+ @GET
+ @AuthRequired
+ @Path("/datafiles/auditFiles")
+ public Response getAuditFiles(@Context ContainerRequestContext crc,
+ @QueryParam("firstId") Long firstId, @QueryParam("lastId") Long lastId,
+ @QueryParam("DatasetIdentifierList") String DatasetIdentifierList) throws WrappedResponse {
+ try {
+ AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
+ if (!user.isSuperuser()) {
+ return error(Response.Status.FORBIDDEN, "Superusers only.");
+ }
+ } catch (WrappedResponse wr) {
+ return wr.getResponse();
+ }
+
+ List failures = new ArrayList<>();
+ int datasetsChecked = 0;
+ long startId = (firstId == null ? 0 : firstId);
+ long endId = (lastId == null ? Long.MAX_VALUE : lastId);
+
+ List datasetIdentifiers;
+ if (DatasetIdentifierList == null || DatasetIdentifierList.isEmpty()) {
+ datasetIdentifiers = Collections.emptyList();
+ } else {
+ startId = 0;
+ endId = Long.MAX_VALUE;
+ datasetIdentifiers = List.of(DatasetIdentifierList.split(","));
+ }
+ if (endId < startId) {
+ return badRequest("Invalid Parameters: lastId must be equal to or greater than firstId");
+ }
+
+ NullSafeJsonBuilder jsonObjectBuilder = NullSafeJsonBuilder.jsonObjectBuilder();
+ if (startId > 0) {
+ jsonObjectBuilder.add("firstId", startId);
+ }
+ if (endId < Long.MAX_VALUE) {
+ jsonObjectBuilder.add("lastId", endId);
+ }
+
+ // compile the list of ids to process
+ List datasetIds;
+ if (datasetIdentifiers.isEmpty()) {
+ datasetIds = datasetService.findAllLocalDatasetIds();
+ } else {
+ datasetIds = new ArrayList<>(datasetIdentifiers.size());
+ JsonArrayBuilder jab = Json.createArrayBuilder();
+ datasetIdentifiers.forEach(id -> {
+ String dId = id.trim();
+ jab.add(dId);
+ Dataset d = datasetService.findByGlobalId(dId);
+ if (d != null) {
+ datasetIds.add(d.getId());
+ } else {
+ failures.add("DatasetIdentifier Not Found: " + dId);
+ }
+ });
+ jsonObjectBuilder.add("DatasetIdentifierList", jab);
+ }
+
+ JsonArrayBuilder jsonDatasetsArrayBuilder = Json.createArrayBuilder();
+ for (Long datasetId : datasetIds) {
+ if (datasetId < startId) {
+ continue;
+ } else if (datasetId > endId) {
+ break;
+ }
+ Dataset dataset;
+ try {
+ dataset = findDatasetOrDie(String.valueOf(datasetId));
+ datasetsChecked++;
+ } catch (WrappedResponse ex) {
+ failures.add("DatasetId:" + datasetId + " Reason:" + ex.getMessage());
+ continue;
+ }
+
+ List missingFiles = new ArrayList<>();
+ List missingFileMetadata = new ArrayList<>();
+ try {
+ Predicate filter = s -> true;
+ StorageIO datasetIO = DataAccess.getStorageIO(dataset);
+ final List result = datasetIO.cleanUp(filter, true);
+ // add files that are in dataset files but not in cleanup result or DataFiles with missing FileMetadata
+ dataset.getFiles().forEach(df -> {
+ try {
+ StorageIO datafileIO = df.getStorageIO();
+ String storageId = df.getStorageIdentifier();
+ FileMetadata fm = df.getFileMetadata();
+ if (!datafileIO.exists()) {
+ missingFiles.add(storageId + ", " + (fm != null ? fm.getLabel() : df.getContentType()));
+ }
+ if (fm == null) {
+ missingFileMetadata.add(storageId + ", DataFile Id:" + df.getId());
+ }
+ } catch (IOException e) {
+ failures.add("DataFileId:" + df.getId() + ", " + e.getMessage());
+ }
+ });
+ } catch (IOException e) {
+ failures.add("DatasetId:" + datasetId + ", " + e.getMessage());
+ }
+
+ JsonObjectBuilder job = Json.createObjectBuilder();
+ if (!missingFiles.isEmpty() || !missingFileMetadata.isEmpty()) {
+ job.add("id", dataset.getId());
+ job.add("identifier", dataset.getIdentifier());
+ job.add("persistentURL", dataset.getPersistentURL());
+ if (!missingFileMetadata.isEmpty()) {
+ JsonArrayBuilder jabMissingFileMetadata = Json.createArrayBuilder();
+ missingFileMetadata.forEach(jabMissingFileMetadata::add);
+ job.add("missingFileMetadata", jabMissingFileMetadata);
+ }
+ if (!missingFiles.isEmpty()) {
+ JsonArrayBuilder jabMissingFiles = Json.createArrayBuilder();
+ missingFiles.forEach(jabMissingFiles::add);
+ job.add("missingFiles", jabMissingFiles);
+ }
+ jsonDatasetsArrayBuilder.add(job);
+ }
+ }
+
+ jsonObjectBuilder.add("datasetsChecked", datasetsChecked);
+ jsonObjectBuilder.add("datasets", jsonDatasetsArrayBuilder);
+ if (!failures.isEmpty()) {
+ JsonArrayBuilder jsonFailuresArrayBuilder = Json.createArrayBuilder();
+ failures.forEach(jsonFailuresArrayBuilder::add);
+ jsonObjectBuilder.add("failures", jsonFailuresArrayBuilder);
+ }
+
+ return ok(jsonObjectBuilder);
+ }
}
diff --git a/src/main/java/edu/harvard/iq/dataverse/dataaccess/S3AccessIO.java b/src/main/java/edu/harvard/iq/dataverse/dataaccess/S3AccessIO.java
index d2fdec7b323..5b9e496281f 100644
--- a/src/main/java/edu/harvard/iq/dataverse/dataaccess/S3AccessIO.java
+++ b/src/main/java/edu/harvard/iq/dataverse/dataaccess/S3AccessIO.java
@@ -753,6 +753,12 @@ public Path getFileSystemPath() throws UnsupportedDataAccessOperationException {
@Override
public boolean exists() {
+ try {
+ key = getMainFileKey();
+ } catch (IOException e) {
+ logger.warning("Caught an IOException in S3AccessIO.exists(): " + e.getMessage());
+ return false;
+ }
String destinationKey = null;
if (dvObject instanceof DataFile) {
destinationKey = key;
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java b/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java
index 6d7dd2eae29..e639a2f011d 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java
@@ -16,6 +16,8 @@
import java.util.HashMap;
import java.util.List;
+import jakarta.json.Json;
+import jakarta.json.JsonArray;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll;
@@ -26,13 +28,11 @@
import java.util.Map;
import java.util.UUID;
-import java.util.logging.Level;
import java.util.logging.Logger;
import static jakarta.ws.rs.core.Response.Status.*;
+import static org.hamcrest.CoreMatchers.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class AdminIT {
@@ -901,6 +901,49 @@ public void testDownloadTmpFile() throws IOException {
.body("message", equalTo("Path must begin with '/tmp' but after normalization was '/etc/passwd'."));
}
+ @Test
+ public void testFindMissingFiles() {
+ Response createUserResponse = UtilIT.createRandomUser();
+ createUserResponse.then().assertThat().statusCode(OK.getStatusCode());
+ String username = UtilIT.getUsernameFromResponse(createUserResponse);
+ String apiToken = UtilIT.getApiTokenFromResponse(createUserResponse);
+ UtilIT.setSuperuserStatus(username, true);
+
+ String dataverseAlias = ":root";
+ Response createDatasetResponse = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken);
+ createDatasetResponse.prettyPrint();
+ createDatasetResponse.then().assertThat().statusCode(CREATED.getStatusCode());
+ int datasetId = JsonPath.from(createDatasetResponse.body().asString()).getInt("data.id");
+ String datasetPersistentId = JsonPath.from(createDatasetResponse.body().asString()).getString("data.persistentId");
+
+ // Upload file
+ Response uploadResponse = UtilIT.uploadRandomFile(datasetPersistentId, apiToken);
+ uploadResponse.then().assertThat().statusCode(CREATED.getStatusCode());
+
+ // Audit files
+ Response resp = UtilIT.auditFiles(apiToken, null, 100L, null);
+ resp.prettyPrint();
+ JsonArray emptyArray = Json.createArrayBuilder().build();
+ resp.then().assertThat()
+ .statusCode(OK.getStatusCode())
+ .body("data.lastId", equalTo(100));
+
+ // Audit files with invalid parameters
+ resp = UtilIT.auditFiles(apiToken, 100L, 0L, null);
+ resp.prettyPrint();
+ resp.then().assertThat()
+ .statusCode(BAD_REQUEST.getStatusCode())
+ .body("status", equalTo("ERROR"))
+ .body("message", equalTo("Invalid Parameters: lastId must be equal to or greater than firstId"));
+
+ // Audit files with list of dataset identifiers parameter
+ resp = UtilIT.auditFiles(apiToken, 1L, null, "bad/id, " + datasetPersistentId);
+ resp.prettyPrint();
+ resp.then().assertThat()
+ .statusCode(OK.getStatusCode())
+ .body("data.failures[0]", equalTo("DatasetIdentifier Not Found: bad/id"));
+ }
+
private String createTestNonSuperuserApiToken() {
Response createUserResponse = UtilIT.createRandomUser();
createUserResponse.then().assertThat().statusCode(OK.getStatusCode());
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 502f1ecb0a8..2fb205f1271 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java
@@ -241,6 +241,22 @@ public static Response clearThumbnailFailureFlag(long fileId) {
return response;
}
+ public static Response auditFiles(String apiToken, Long firstId, Long lastId, String csvList) {
+ String params = "";
+ if (firstId != null) {
+ params = "?firstId="+ firstId;
+ }
+ if (lastId != null) {
+ params = params + (params.isEmpty() ? "?" : "&") + "lastId="+ lastId;
+ }
+ if (csvList != null) {
+ params = params + (params.isEmpty() ? "?" : "&") + "DatasetIdentifierList="+ csvList;
+ }
+ return given()
+ .header(API_TOKEN_HTTP_HEADER, apiToken)
+ .get("/api/admin/datafiles/auditFiles" + params);
+ }
+
private static String getAuthenticatedUserAsJsonString(String persistentUserId, String firstName, String lastName, String authenticationProviderId, String identifier) {
JsonObjectBuilder builder = Json.createObjectBuilder();
builder.add("authenticationProviderId", authenticationProviderId);
From c9b685aa942c156670aaa78355bf57b45accd20d Mon Sep 17 00:00:00 2001
From: Jim Myers
Date: Wed, 13 Nov 2024 11:40:22 -0500
Subject: [PATCH 119/270] Update flyway script
---
..._10017-failure-with-long-custom-question.sql => V6.4.0.2.sql} | 1 +
1 file changed, 1 insertion(+)
rename src/main/resources/db/migration/{V6.1.0.6__10017-failure-with-long-custom-question.sql => V6.4.0.2.sql} (90%)
diff --git a/src/main/resources/db/migration/V6.1.0.6__10017-failure-with-long-custom-question.sql b/src/main/resources/db/migration/V6.4.0.2.sql
similarity index 90%
rename from src/main/resources/db/migration/V6.1.0.6__10017-failure-with-long-custom-question.sql
rename to src/main/resources/db/migration/V6.4.0.2.sql
index 9a3002378b3..414e98975db 100644
--- a/src/main/resources/db/migration/V6.1.0.6__10017-failure-with-long-custom-question.sql
+++ b/src/main/resources/db/migration/V6.4.0.2.sql
@@ -1 +1,2 @@
+#10118
ALTER TABLE customquestion ALTER COLUMN questionstring TYPE text;
From 89892a4862b780d47a90699c729c798726a2c507 Mon Sep 17 00:00:00 2001
From: qqmyers
Date: Wed, 13 Nov 2024 12:52:45 -0500
Subject: [PATCH 120/270] getget typo
---
.../edu/harvard/iq/dataverse/DatasetVersionDifference.java | 2 +-
.../harvard/iq/dataverse/DatasetVersionDifferenceTest.java | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java
index 19c3b02f4ee..27868e3c7ed 100644
--- a/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java
+++ b/src/main/java/edu/harvard/iq/dataverse/DatasetVersionDifference.java
@@ -1616,7 +1616,7 @@ private static boolean fieldsAreDifferent(DatasetField originalField, DatasetFie
return false;
}
- List getgetChangedVariableMetadata() {
+ List getChangedVariableMetadata() {
return changedVariableMetadata;
}
diff --git a/src/test/java/edu/harvard/iq/dataverse/DatasetVersionDifferenceTest.java b/src/test/java/edu/harvard/iq/dataverse/DatasetVersionDifferenceTest.java
index 44ac267abaf..8508c9ac34e 100644
--- a/src/test/java/edu/harvard/iq/dataverse/DatasetVersionDifferenceTest.java
+++ b/src/test/java/edu/harvard/iq/dataverse/DatasetVersionDifferenceTest.java
@@ -221,7 +221,7 @@ private void compareResults(DatasetVersion datasetVersion, DatasetVersion datase
assertEquals(addedFiles, diff.getAddedFiles());
assertEquals(removedFiles, diff.getRemovedFiles());
assertEquals(changedFileMetadata, diff.getChangedFileMetadata());
- assertEquals(changedVariableMetadata, diff.getgetChangedVariableMetadata());
+ assertEquals(changedVariableMetadata, diff.getChangedVariableMetadata());
assertEquals(replacedFiles.size(), diff.getReplacedFiles().size());
for (int i = 0; i < replacedFiles.size(); i++) {
assertEquals(replacedFiles.get(i)[0], diff.getReplacedFiles().get(i)[0]);
@@ -233,7 +233,7 @@ private void compareResults(DatasetVersion datasetVersion, DatasetVersion datase
assertEquals(expectedAddedFiles, diff.getAddedFiles());
assertEquals(expectedRemovedFiles, diff.getRemovedFiles());
assertEquals(expectedChangedFileMetadata, diff.getChangedFileMetadata());
- assertEquals(expectedChangedVariableMetadata, diff.getgetChangedVariableMetadata());
+ assertEquals(expectedChangedVariableMetadata, diff.getChangedVariableMetadata());
assertEquals(expectedReplacedFiles.size(), diff.getReplacedFiles().size());
for (int i = 0; i < expectedReplacedFiles.size(); i++) {
assertEquals(expectedReplacedFiles.get(i)[0], diff.getReplacedFiles().get(i)[0]);
From a663ad443e44ae2b01c8fde66f4234763e73c3c1 Mon Sep 17 00:00:00 2001
From: Jim Myers
Date: Wed, 13 Nov 2024 15:29:08 -0500
Subject: [PATCH 121/270] fix comment
---
src/main/resources/db/migration/V6.4.0.2.sql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/resources/db/migration/V6.4.0.2.sql b/src/main/resources/db/migration/V6.4.0.2.sql
index 414e98975db..bc4a85b278f 100644
--- a/src/main/resources/db/migration/V6.4.0.2.sql
+++ b/src/main/resources/db/migration/V6.4.0.2.sql
@@ -1,2 +1,2 @@
-#10118
+-- #10118
ALTER TABLE customquestion ALTER COLUMN questionstring TYPE text;
From 646ebd5a4f408f56bc7bfdbefc53c17a09724b26 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9r=C3=B4me=20ROUCOU?=
Date: Thu, 14 Nov 2024 10:59:02 +0100
Subject: [PATCH 122/270] Review made by pdurbin
---
doc/sphinx-guides/source/api/native-api.rst | 4 +-
.../iq/dataverse/api/SavedSearches.java | 2 +-
.../iq/dataverse/api/SavedSearchIT.java | 58 +++++--------------
.../edu/harvard/iq/dataverse/api/UtilIT.java | 33 ++++++++++-
4 files changed, 48 insertions(+), 49 deletions(-)
diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst
index 6254742eebb..54f9eed2703 100644
--- a/doc/sphinx-guides/source/api/native-api.rst
+++ b/doc/sphinx-guides/source/api/native-api.rst
@@ -6079,7 +6079,7 @@ Saved Search
~~~~~~~~~~~~
The Saved Search, Linked Dataverses, and Linked Datasets features are only accessible to superusers except for linking a dataset. The following API endpoints were added to help people with access to the "admin" API make use of these features in their current form. Keep in mind that they are partially experimental.
-The update of all saved search is run by a timer once a week (See :ref:`saved-search-timer`) so if you just created a saved search, you can run manually ``makelinks`` endpoint that will find new dataverses and datasets that match the saved search and then link the search results to the dataverse in which the saved search is defined.
+The update of all saved search is run by a timer once a week (See :ref:`saved-search-timer`) so if you just created a saved search, you can run manually the ``makelinks`` endpoint that will find new dataverses and datasets that match the saved search and then link the search results to the dataverse in which the saved search is defined.
List all saved searches. ::
@@ -6091,7 +6091,7 @@ List a saved search by database id. ::
Delete a saved search by database id.
-The ``unlink=true`` query parameter unlinks all links (linked dataset or Dataverse collection) associated with the deleted saved search. Use of this parameter should be well considered as you cannot know if the links were created manually or by the saved search. After deleting a saved search with ``unlink=true``, we recommend running ``/makelinks/all`` just in case there was a dataset that was linked by another saved search. (Saved searches can link the same dataset.) Reindexing might be necessary as well.::
+The ``unlink=true`` query parameter unlinks all links (linked dataset or Dataverse collection) associated with the deleted saved search. Use of this parameter should be well considered as you cannot know if the links were created manually or by the saved search. After deleting a saved search with ``unlink=true``, we recommend running ``/makelinks/all`` just in case there was a dataset that was linked by another saved search. (Saved searches can link the same dataset.) Reindexing might be necessary as well. ::
DELETE http://$SERVER/api/admin/savedsearches/$id?unlink=true
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/SavedSearches.java b/src/main/java/edu/harvard/iq/dataverse/api/SavedSearches.java
index 33a11a2df23..e6519c9ff36 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/SavedSearches.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/SavedSearches.java
@@ -181,7 +181,7 @@ public Response delete(@PathParam("id") long doomedId, @QueryParam("unlink") boo
try {
wasDeleted = savedSearchSvc.delete(doomedId, unlink);
} catch (Exception e) {
- return error(INTERNAL_SERVER_ERROR, "Problem while trying to unlink links of saved search id " + doomedId);
+ return error(INTERNAL_SERVER_ERROR, "Problem while trying to unlink links of saved search id " + doomedId + ". Exception: " + e.getLocalizedMessage());
}
if (wasDeleted) {
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/SavedSearchIT.java b/src/test/java/edu/harvard/iq/dataverse/api/SavedSearchIT.java
index 90357596c25..08ebec31cd6 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/SavedSearchIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/SavedSearchIT.java
@@ -22,7 +22,7 @@ public class SavedSearchIT {
@BeforeAll
public static void setUpClass() {
-
+ RestAssured.baseURI = UtilIT.getRestAssuredBaseUri();
}
@AfterAll
@@ -53,81 +53,55 @@ public void testSavedSearches() {
Integer datasetId2 = UtilIT.getDatasetIdFromResponse(createDatasetResponse2);
// missing body
- Response resp = RestAssured.given()
- .contentType("application/json")
- .post("/api/admin/savedsearches");
+ Response resp = UtilIT.setSavedSearch();
resp.prettyPrint();
resp.then().assertThat()
.statusCode(INTERNAL_SERVER_ERROR.getStatusCode());
// creatorId null
- resp = RestAssured.given()
- .body(createSavedSearchJson("*", null, dataverseId, "subject_ss:Medicine, Health and Life Sciences"))
- .contentType("application/json")
- .post("/api/admin/savedsearches");
+ resp = UtilIT.setSavedSearch(createSavedSearchJson("*", null, dataverseId, "subject_ss:Medicine, Health and Life Sciences"));
resp.prettyPrint();
resp.then().assertThat()
.statusCode(BAD_REQUEST.getStatusCode());
// creatorId string
- resp = RestAssured.given()
- .body(createSavedSearchJson("*", "1", dataverseId.toString(), "subject_ss:Medicine, Health and Life Sciences"))
- .contentType("application/json")
- .post("/api/admin/savedsearches");
+ resp = UtilIT.setSavedSearch(createSavedSearchJson("*", "1", dataverseId.toString(), "subject_ss:Medicine, Health and Life Sciences"));
resp.prettyPrint();
resp.then().assertThat()
.statusCode(BAD_REQUEST.getStatusCode());
// creatorId not found
- resp = RestAssured.given()
- .body(createSavedSearchJson("*", 9999, dataverseId, "subject_ss:Medicine, Health and Life Sciences"))
- .contentType("application/json")
- .post("/api/admin/savedsearches");
+ resp = UtilIT.setSavedSearch(createSavedSearchJson("*", 9999, dataverseId, "subject_ss:Medicine, Health and Life Sciences"));
resp.prettyPrint();
resp.then().assertThat()
.statusCode(NOT_FOUND.getStatusCode());
// definitionPointId null
- resp = RestAssured.given()
- .body(createSavedSearchJson("*", 1, null, "subject_ss:Medicine, Health and Life Sciences"))
- .contentType("application/json")
- .post("/api/admin/savedsearches");
+ resp = UtilIT.setSavedSearch(createSavedSearchJson("*", 1, null, "subject_ss:Medicine, Health and Life Sciences"));
resp.prettyPrint();
resp.then().assertThat()
.statusCode(BAD_REQUEST.getStatusCode());
// definitionPointId string
- resp = RestAssured.given()
- .body(createSavedSearchJson("*", "1", "9999", "subject_ss:Medicine, Health and Life Sciences"))
- .contentType("application/json")
- .post("/api/admin/savedsearches");
+ resp = UtilIT.setSavedSearch(createSavedSearchJson("*", "1", "9999", "subject_ss:Medicine, Health and Life Sciences"));
resp.prettyPrint();
resp.then().assertThat()
.statusCode(BAD_REQUEST.getStatusCode());
// definitionPointId not found
- resp = RestAssured.given()
- .body(createSavedSearchJson("*", 1, 9999, "subject_ss:Medicine, Health and Life Sciences"))
- .contentType("application/json")
- .post("/api/admin/savedsearches");
+ resp = UtilIT.setSavedSearch(createSavedSearchJson("*", 1, 9999, "subject_ss:Medicine, Health and Life Sciences"));
resp.prettyPrint();
resp.then().assertThat()
.statusCode(NOT_FOUND.getStatusCode());
// missing filter
- resp = RestAssured.given()
- .body(createSavedSearchJson("*", 1, dataverseId))
- .contentType("application/json")
- .post("/api/admin/savedsearches");
+ resp = UtilIT.setSavedSearch(createSavedSearchJson("*", 1, dataverseId));
resp.prettyPrint();
resp.then().assertThat()
.statusCode(OK.getStatusCode());
// create a saved search as superuser : OK
- resp = RestAssured.given()
- .body(createSavedSearchJson("*", 1, dataverseId, "subject_ss:Medicine, Health and Life Sciences"))
- .contentType("application/json")
- .post("/api/admin/savedsearches");
+ resp = UtilIT.setSavedSearch(createSavedSearchJson("*", 1, dataverseId, "subject_ss:Medicine, Health and Life Sciences"));
resp.prettyPrint();
resp.then().assertThat()
.statusCode(OK.getStatusCode());
@@ -136,8 +110,7 @@ public void testSavedSearches() {
Integer createdSavedSearchId = path.getInt("data.id");
// get list as non superuser : OK
- Response getListReponse = RestAssured.given()
- .get("/api/admin/savedsearches/list");
+ Response getListReponse = UtilIT.getSavedSearchList();
getListReponse.prettyPrint();
getListReponse.then().assertThat()
.statusCode(OK.getStatusCode());
@@ -146,22 +119,19 @@ public void testSavedSearches() {
List
@@ -1254,7 +1254,7 @@
#{bundle['copyClipboard']}
-
+
From b64addc288607f9bfb03acef00274a862757f3d4 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Tue, 19 Nov 2024 14:03:57 -0500
Subject: [PATCH 168/270] reformat json output
---
doc/sphinx-guides/source/api/native-api.rst | 15 ++++--
.../edu/harvard/iq/dataverse/api/Admin.java | 53 +++++++++++++------
.../edu/harvard/iq/dataverse/api/AdminIT.java | 3 +-
.../edu/harvard/iq/dataverse/api/UtilIT.java | 2 +-
4 files changed, 52 insertions(+), 21 deletions(-)
diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst
index 2f7bc0a4880..c4eaa405efb 100644
--- a/doc/sphinx-guides/source/api/native-api.rst
+++ b/doc/sphinx-guides/source/api/native-api.rst
@@ -6237,7 +6237,10 @@ Sample JSON Audit Response::
"identifier": "FK2/JXYBJS",
"persistentURL": "https://doi.org/10.5072/FK2/JXYBJS",
"missingFileMetadata": [
- "local://1930cce4f2d-855ccc51fcbb, DataFile Id:7"
+ {
+ "StorageIdentifier": "local://1930cce4f2d-855ccc51fcbb",
+ "DataFileId": "7"
+ }
]
},
{
@@ -6245,12 +6248,18 @@ Sample JSON Audit Response::
"identifier": "DVN/MPU019",
"persistentURL": "https://doi.org/10.7910/DVN/MPU019",
"missingFiles": [
- "s3://dvn-cloud:298910, jihad_metadata_edited.csv"
+ {
+ "StorageIdentifier": "s3://dvn-cloud:298910",
+ "label": "jihad_metadata_edited.csv"
+ }
]
}
],
"failures": [
- "DatasetIdentifier Not Found: doi.org/10.5072/FK2/XXXXXX"
+ {
+ "DatasetIdentifier": "doi.org/10.5072/FK2/XXXXXX",
+ "Reason": "Not Found"
+ }
]
}
}
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java
index ac01d669ef0..774ee675949 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java
@@ -2536,7 +2536,6 @@ public Response getAuditFiles(@Context ContainerRequestContext crc,
return wr.getResponse();
}
- List failures = new ArrayList<>();
int datasetsChecked = 0;
long startId = (firstId == null ? 0 : firstId);
long endId = (lastId == null ? Long.MAX_VALUE : lastId);
@@ -2554,6 +2553,9 @@ public Response getAuditFiles(@Context ContainerRequestContext crc,
}
NullSafeJsonBuilder jsonObjectBuilder = NullSafeJsonBuilder.jsonObjectBuilder();
+ JsonArrayBuilder jsonDatasetsArrayBuilder = Json.createArrayBuilder();
+ JsonArrayBuilder jsonFailuresArrayBuilder = Json.createArrayBuilder();
+
if (startId > 0) {
jsonObjectBuilder.add("firstId", startId);
}
@@ -2575,13 +2577,15 @@ public Response getAuditFiles(@Context ContainerRequestContext crc,
if (d != null) {
datasetIds.add(d.getId());
} else {
- failures.add("DatasetIdentifier Not Found: " + dId);
+ NullSafeJsonBuilder job = NullSafeJsonBuilder.jsonObjectBuilder();
+ job.add("DatasetIdentifier",dId);
+ job.add("Reason","Not Found");
+ jsonFailuresArrayBuilder.add(job);
}
});
jsonObjectBuilder.add("DatasetIdentifierList", jab);
}
- JsonArrayBuilder jsonDatasetsArrayBuilder = Json.createArrayBuilder();
for (Long datasetId : datasetIds) {
if (datasetId < startId) {
continue;
@@ -2592,8 +2596,11 @@ public Response getAuditFiles(@Context ContainerRequestContext crc,
try {
dataset = findDatasetOrDie(String.valueOf(datasetId));
datasetsChecked++;
- } catch (WrappedResponse ex) {
- failures.add("DatasetId:" + datasetId + " Reason:" + ex.getMessage());
+ } catch (WrappedResponse e) {
+ NullSafeJsonBuilder job = NullSafeJsonBuilder.jsonObjectBuilder();
+ job.add("DatasetId", datasetId);
+ job.add("Reason", e.getMessage());
+ jsonFailuresArrayBuilder.add(job);
continue;
}
@@ -2610,17 +2617,23 @@ public Response getAuditFiles(@Context ContainerRequestContext crc,
String storageId = df.getStorageIdentifier();
FileMetadata fm = df.getFileMetadata();
if (!datafileIO.exists()) {
- missingFiles.add(storageId + ", " + (fm != null ? fm.getLabel() : df.getContentType()));
+ missingFiles.add(storageId + "," + (fm != null ? "label,"+fm.getLabel() : "type,"+df.getContentType()));
}
if (fm == null) {
- missingFileMetadata.add(storageId + ", DataFile Id:" + df.getId());
+ missingFileMetadata.add(storageId + ",DataFileId," + df.getId());
}
} catch (IOException e) {
- failures.add("DataFileId:" + df.getId() + ", " + e.getMessage());
+ NullSafeJsonBuilder job = NullSafeJsonBuilder.jsonObjectBuilder();
+ job.add("DataFileId", df.getId());
+ job.add("Reason", e.getMessage());
+ jsonFailuresArrayBuilder.add(job);
}
});
} catch (IOException e) {
- failures.add("DatasetId:" + datasetId + ", " + e.getMessage());
+ NullSafeJsonBuilder job = NullSafeJsonBuilder.jsonObjectBuilder();
+ job.add("DatasetId", datasetId);
+ job.add("Reason", e.getMessage());
+ jsonFailuresArrayBuilder.add(job);
}
JsonObjectBuilder job = Json.createObjectBuilder();
@@ -2630,12 +2643,24 @@ public Response getAuditFiles(@Context ContainerRequestContext crc,
job.add("persistentURL", dataset.getPersistentURL());
if (!missingFileMetadata.isEmpty()) {
JsonArrayBuilder jabMissingFileMetadata = Json.createArrayBuilder();
- missingFileMetadata.forEach(jabMissingFileMetadata::add);
+ missingFileMetadata.forEach(mm -> {
+ String[] missingMetadata = mm.split(",");
+ NullSafeJsonBuilder jobj = NullSafeJsonBuilder.jsonObjectBuilder()
+ .add("StorageIdentifier", missingMetadata[0])
+ .add(missingMetadata[1], missingMetadata[2]);
+ jabMissingFileMetadata.add(jobj);
+ });
job.add("missingFileMetadata", jabMissingFileMetadata);
}
if (!missingFiles.isEmpty()) {
JsonArrayBuilder jabMissingFiles = Json.createArrayBuilder();
- missingFiles.forEach(jabMissingFiles::add);
+ missingFiles.forEach(mf -> {
+ String[] missingFile = mf.split(",");
+ NullSafeJsonBuilder jobj = NullSafeJsonBuilder.jsonObjectBuilder()
+ .add("StorageIdentifier", missingFile[0])
+ .add(missingFile[1], missingFile[2]);
+ jabMissingFiles.add(jobj);
+ });
job.add("missingFiles", jabMissingFiles);
}
jsonDatasetsArrayBuilder.add(job);
@@ -2644,11 +2669,7 @@ public Response getAuditFiles(@Context ContainerRequestContext crc,
jsonObjectBuilder.add("datasetsChecked", datasetsChecked);
jsonObjectBuilder.add("datasets", jsonDatasetsArrayBuilder);
- if (!failures.isEmpty()) {
- JsonArrayBuilder jsonFailuresArrayBuilder = Json.createArrayBuilder();
- failures.forEach(jsonFailuresArrayBuilder::add);
- jsonObjectBuilder.add("failures", jsonFailuresArrayBuilder);
- }
+ jsonObjectBuilder.add("failures", jsonFailuresArrayBuilder);
return ok(jsonObjectBuilder);
}
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java b/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java
index e639a2f011d..84011d7ac73 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java
@@ -941,7 +941,8 @@ public void testFindMissingFiles() {
resp.prettyPrint();
resp.then().assertThat()
.statusCode(OK.getStatusCode())
- .body("data.failures[0]", equalTo("DatasetIdentifier Not Found: bad/id"));
+ .body("data.failures[0].DatasetIdentifier", equalTo("bad/id"))
+ .body("data.failures[0].Reason", equalTo("Not Found"));
}
private String createTestNonSuperuserApiToken() {
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 2fb205f1271..c450c587543 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java
@@ -250,7 +250,7 @@ public static Response auditFiles(String apiToken, Long firstId, Long lastId, St
params = params + (params.isEmpty() ? "?" : "&") + "lastId="+ lastId;
}
if (csvList != null) {
- params = params + (params.isEmpty() ? "?" : "&") + "DatasetIdentifierList="+ csvList;
+ params = params + (params.isEmpty() ? "?" : "&") + "datasetIdentifierList="+ csvList;
}
return given()
.header(API_TOKEN_HTTP_HEADER, apiToken)
From e89f1ca91e939d99521b2eeddc3643dd4333b579 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Tue, 19 Nov 2024 15:17:34 -0500
Subject: [PATCH 169/270] reformat json output
---
src/main/java/edu/harvard/iq/dataverse/api/Admin.java | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java
index 774ee675949..29f366d91b9 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java
@@ -2640,6 +2640,8 @@ public Response getAuditFiles(@Context ContainerRequestContext crc,
if (!missingFiles.isEmpty() || !missingFileMetadata.isEmpty()) {
job.add("id", dataset.getId());
job.add("identifier", dataset.getIdentifier());
+ job.add("authority", dataset.getAuthority());
+ job.add("protocol", dataset.getProtocol());
job.add("persistentURL", dataset.getPersistentURL());
if (!missingFileMetadata.isEmpty()) {
JsonArrayBuilder jabMissingFileMetadata = Json.createArrayBuilder();
From 7e9aae98574e357b4b64b1feb024a89fe2c9dac2 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Tue, 19 Nov 2024 15:19:28 -0500
Subject: [PATCH 170/270] reformat json output
---
doc/sphinx-guides/source/api/native-api.rst | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst
index c4eaa405efb..b9c30d71fa2 100644
--- a/doc/sphinx-guides/source/api/native-api.rst
+++ b/doc/sphinx-guides/source/api/native-api.rst
@@ -6235,6 +6235,8 @@ Sample JSON Audit Response::
{
"id": 6,
"identifier": "FK2/JXYBJS",
+ "authority": "10.5072",
+ "protocol": "doi",
"persistentURL": "https://doi.org/10.5072/FK2/JXYBJS",
"missingFileMetadata": [
{
@@ -6246,6 +6248,8 @@ Sample JSON Audit Response::
{
"id": 47731,
"identifier": "DVN/MPU019",
+ "authority": "10.7910",
+ "protocol": "doi",
"persistentURL": "https://doi.org/10.7910/DVN/MPU019",
"missingFiles": [
{
From 11cbe8515e0c60443d76c78ce972ca4f1c83c16a Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Tue, 19 Nov 2024 15:42:27 -0500
Subject: [PATCH 171/270] reformat json output
---
doc/release-notes/220-harvard-edu-audit-files.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/release-notes/220-harvard-edu-audit-files.md b/doc/release-notes/220-harvard-edu-audit-files.md
index 160aa2e4b2f..c697bc225c0 100644
--- a/doc/release-notes/220-harvard-edu-audit-files.md
+++ b/doc/release-notes/220-harvard-edu-audit-files.md
@@ -11,6 +11,6 @@ The JSON response includes:
curl "http://localhost:8080/api/admin/datafiles/auditFiles
curl "http://localhost:8080/api/admin/datafiles/auditFiles?firstId=0&lastId=1000"
-curl "http://localhost:8080/api/admin/datafiles/auditFiles?DatasetIdentifierList=doi:10.5072/FK2/RVNT9Q,doi:10.5072/FK2/RVNT9Q
+curl "http://localhost:8080/api/admin/datafiles/auditFiles?datasetIdentifierList=doi:10.5072/FK2/RVNT9Q,doi:10.5072/FK2/RVNT9Q
For more information, see issue [the docs](https://dataverse-guide--11016.org.readthedocs.build/en/11016/api/native-api.html#datafile-audit), #11016, and [#220](https://github.com/IQSS/dataverse.harvard.edu/issues/220)
From 62937872c5cb7b408ce90cd79f26f6cf45921721 Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Tue, 19 Nov 2024 16:37:05 -0500
Subject: [PATCH 172/270] a quick bug fix; changed verbose logging to .fine.
#10977
---
.../datasetutility/OptionalFileParams.java | 6 ++++++
.../impl/CreateNewDataFilesCommand.java | 2 --
.../iq/dataverse/globus/GlobusServiceBean.java | 18 ++++++++++++------
3 files changed, 18 insertions(+), 8 deletions(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/datasetutility/OptionalFileParams.java b/src/main/java/edu/harvard/iq/dataverse/datasetutility/OptionalFileParams.java
index c1be6424a84..54844160163 100644
--- a/src/main/java/edu/harvard/iq/dataverse/datasetutility/OptionalFileParams.java
+++ b/src/main/java/edu/harvard/iq/dataverse/datasetutility/OptionalFileParams.java
@@ -39,6 +39,12 @@
* - Provenance related information
*
* @author rmp553
+ * @todo (?) We may want to consider renaming this class to DataFileParams or
+ * DataFileInfo... it was originally created to encode some bits of info -
+ * the file "tags" specifically, that didn't fit in elsewhere in the normal
+ * workflow; but it's been expanded to cover pretty much everything else associated
+ * with DataFiles and it's not really "optional" anymore when, for example, used
+ * in the direct upload workflow. (?)
*/
public class OptionalFileParams {
diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/CreateNewDataFilesCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/CreateNewDataFilesCommand.java
index 172c92dc1fd..e9a2025b112 100644
--- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/CreateNewDataFilesCommand.java
+++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/CreateNewDataFilesCommand.java
@@ -640,7 +640,6 @@ public CreateDataFileResult execute(CommandContext ctxt) throws CommandException
createIngestFailureReport(datafile, warningMessage);
datafile.SetIngestProblem();
}
- logger.info("datafile size: " + datafile.getFilesize());
if (datafile.getFilesize() < 0) {
datafile.setFilesize(fileSize);
}
@@ -659,7 +658,6 @@ public CreateDataFileResult execute(CommandContext ctxt) throws CommandException
quota.setTotalUsageInBytes(quota.getTotalUsageInBytes() + fileSize);
}
- logger.info("datafile size (again): " + datafile.getFilesize());
return CreateDataFileResult.success(fileName, finalType, datafiles);
}
diff --git a/src/main/java/edu/harvard/iq/dataverse/globus/GlobusServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/globus/GlobusServiceBean.java
index 013fefd1e34..3d1c5a1044d 100644
--- a/src/main/java/edu/harvard/iq/dataverse/globus/GlobusServiceBean.java
+++ b/src/main/java/edu/harvard/iq/dataverse/globus/GlobusServiceBean.java
@@ -285,12 +285,11 @@ private int makeDir(GlobusEndpoint endpoint, String dir) {
return result.status;
}
- private Map lookupFileSizes(GlobusEndpoint endpoint, String dir) {
- Map ret = new HashMap<>();
-
+ private Map lookupFileSizes(GlobusEndpoint endpoint, String dir) {
MakeRequestResponse result;
try {
+ logger.fine("Attempting to look up the contents of the Globus folder "+dir);
URL url = new URL(
"https://transfer.api.globusonline.org/v0.10/operation/endpoint/" + endpoint.getId()
+ "/ls?path=" + dir);
@@ -303,13 +302,16 @@ private Map lookupFileSizes(GlobusEndpoint endpoint, String dir) {
default:
logger.warning("Status " + result.status + " received when looking up dir " + dir);
logger.fine("Response: " + result.jsonResponse);
+ return null;
}
} catch (MalformedURLException ex) {
// Misconfiguration
- logger.warning("Failed to create dir on " + endpoint.getId());
+ logger.warning("Failed to list the contents of the directory "+ dir + " on endpoint " + endpoint.getId());
return null;
}
+ Map ret = new HashMap<>();
+
JsonObject listObject = JsonUtil.getJsonObject(result.jsonResponse);
JsonArray dataArray = listObject.getJsonArray("DATA");
@@ -317,6 +319,8 @@ private Map lookupFileSizes(GlobusEndpoint endpoint, String dir) {
for (int i = 0; i < dataArray.size(); i++) {
String dataType = dataArray.getJsonObject(i).getString("DATA_TYPE", null);
if (dataType != null && dataType.equals("file")) {
+ // is it safe to assume that any entry with a valid "DATA_TYPE": "file"
+ // will also have valid "name" and "size" entries?
String fileName = dataArray.getJsonObject(i).getString("name");
long fileSize = dataArray.getJsonObject(i).getJsonNumber("size").longValueExact();
ret.put(fileName, fileSize);
@@ -1020,17 +1024,19 @@ private void processUploadedFiles(JsonArray filesJsonArray, Dataset dataset, Aut
patch = Json.createPatchBuilder()
.add("/mimeType", newfileJsonObject.get(0).getString("mime")).build();
fileJsonObject = patch.apply(fileJsonObject);
- addFilesJsonData.add(fileJsonObject);
// If we already know the size of this file on the Globus end,
// we'll pass it to /addFiles, to avoid looking up file sizes
// one by one:
if (fileSizeMap != null && fileSizeMap.get(fileId) != null) {
Long uploadedFileSize = fileSizeMap.get(fileId);
- myLogger.fine("Found size for file " + fileId + ": " + uploadedFileSize + " bytes");
+ myLogger.info("Found size for file " + fileId + ": " + uploadedFileSize + " bytes");
patch = Json.createPatchBuilder()
.add("/fileSize", Json.createValue(uploadedFileSize)).build();
fileJsonObject = patch.apply(fileJsonObject);
+ } else {
+ logger.warning("No file size entry found for file "+fileId);
}
+ addFilesJsonData.add(fileJsonObject);
countSuccess++;
} else {
myLogger.info(fileName
From 3eec3663c96835b14f6a6444b8bc0055b4835241 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Tue, 19 Nov 2024 16:53:28 -0500
Subject: [PATCH 173/270] adding directory label to json and changing camelCase
---
doc/sphinx-guides/source/api/native-api.rst | 17 +++++-----
.../edu/harvard/iq/dataverse/api/Admin.java | 32 +++++++++++--------
.../edu/harvard/iq/dataverse/api/AdminIT.java | 4 +--
3 files changed, 29 insertions(+), 24 deletions(-)
diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst
index b9c30d71fa2..84e8bf45d9d 100644
--- a/doc/sphinx-guides/source/api/native-api.rst
+++ b/doc/sphinx-guides/source/api/native-api.rst
@@ -6216,7 +6216,7 @@ For auditing the Datasets in a paged manor (firstId and lastId)::
Auditing specific Datasets (comma separated list)::
- curl "$SERVER_URL/api/admin/datafiles/auditFiles?datasetIdentifierList=doi.org/10.5072/FK2/JXYBJS,doi.org/10.7910/DVN/MPU019
+ curl "$SERVER_URL/api/admin/datafiles/auditFiles?datasetIdentifierList=doi:10.5072/FK2/JXYBJS,doi:10.7910/DVN/MPU019
Sample JSON Audit Response::
@@ -6225,7 +6225,7 @@ Sample JSON Audit Response::
"data": {
"firstId": 0,
"lastId": 100,
- "DatasetIdentifierList": [
+ "datasetIdentifierList": [
"doi.org/10.5072/FK2/XXXXXX",
"doi.org/10.5072/FK2/JXYBJS",
"doi.org/10.7910/DVN/MPU019"
@@ -6240,8 +6240,8 @@ Sample JSON Audit Response::
"persistentURL": "https://doi.org/10.5072/FK2/JXYBJS",
"missingFileMetadata": [
{
- "StorageIdentifier": "local://1930cce4f2d-855ccc51fcbb",
- "DataFileId": "7"
+ "storageIdentifier": "local://1930cce4f2d-855ccc51fcbb",
+ "dataFileId": "7"
}
]
},
@@ -6253,16 +6253,17 @@ Sample JSON Audit Response::
"persistentURL": "https://doi.org/10.7910/DVN/MPU019",
"missingFiles": [
{
- "StorageIdentifier": "s3://dvn-cloud:298910",
- "label": "jihad_metadata_edited.csv"
+ "storageIdentifier": "s3://dvn-cloud:298910",
+ "directoryLabel": "trees",
+ "label": "trees.png"
}
]
}
],
"failures": [
{
- "DatasetIdentifier": "doi.org/10.5072/FK2/XXXXXX",
- "Reason": "Not Found"
+ "datasetIdentifier": "doi.org/10.5072/FK2/XXXXXX",
+ "reason": "Not Found"
}
]
}
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java
index 29f366d91b9..793e472ddac 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java
@@ -2578,12 +2578,12 @@ public Response getAuditFiles(@Context ContainerRequestContext crc,
datasetIds.add(d.getId());
} else {
NullSafeJsonBuilder job = NullSafeJsonBuilder.jsonObjectBuilder();
- job.add("DatasetIdentifier",dId);
- job.add("Reason","Not Found");
+ job.add("datasetIdentifier",dId);
+ job.add("reason","Not Found");
jsonFailuresArrayBuilder.add(job);
}
});
- jsonObjectBuilder.add("DatasetIdentifierList", jab);
+ jsonObjectBuilder.add("datasetIdentifierList", jab);
}
for (Long datasetId : datasetIds) {
@@ -2598,8 +2598,8 @@ public Response getAuditFiles(@Context ContainerRequestContext crc,
datasetsChecked++;
} catch (WrappedResponse e) {
NullSafeJsonBuilder job = NullSafeJsonBuilder.jsonObjectBuilder();
- job.add("DatasetId", datasetId);
- job.add("Reason", e.getMessage());
+ job.add("datasetId", datasetId);
+ job.add("reason", e.getMessage());
jsonFailuresArrayBuilder.add(job);
continue;
}
@@ -2617,22 +2617,24 @@ public Response getAuditFiles(@Context ContainerRequestContext crc,
String storageId = df.getStorageIdentifier();
FileMetadata fm = df.getFileMetadata();
if (!datafileIO.exists()) {
- missingFiles.add(storageId + "," + (fm != null ? "label,"+fm.getLabel() : "type,"+df.getContentType()));
+ missingFiles.add(storageId + "," + (fm != null ?
+ (fm.getDirectoryLabel() != null || !fm.getDirectoryLabel().isEmpty() ? "directoryLabel,"+fm.getDirectoryLabel()+"," : "")
+ +"label,"+fm.getLabel() : "type,"+df.getContentType()));
}
if (fm == null) {
- missingFileMetadata.add(storageId + ",DataFileId," + df.getId());
+ missingFileMetadata.add(storageId + ",dataFileId," + df.getId());
}
} catch (IOException e) {
NullSafeJsonBuilder job = NullSafeJsonBuilder.jsonObjectBuilder();
- job.add("DataFileId", df.getId());
- job.add("Reason", e.getMessage());
+ job.add("dataFileId", df.getId());
+ job.add("reason", e.getMessage());
jsonFailuresArrayBuilder.add(job);
}
});
} catch (IOException e) {
NullSafeJsonBuilder job = NullSafeJsonBuilder.jsonObjectBuilder();
- job.add("DatasetId", datasetId);
- job.add("Reason", e.getMessage());
+ job.add("datasetId", datasetId);
+ job.add("reason", e.getMessage());
jsonFailuresArrayBuilder.add(job);
}
@@ -2648,7 +2650,7 @@ public Response getAuditFiles(@Context ContainerRequestContext crc,
missingFileMetadata.forEach(mm -> {
String[] missingMetadata = mm.split(",");
NullSafeJsonBuilder jobj = NullSafeJsonBuilder.jsonObjectBuilder()
- .add("StorageIdentifier", missingMetadata[0])
+ .add("storageIdentifier", missingMetadata[0])
.add(missingMetadata[1], missingMetadata[2]);
jabMissingFileMetadata.add(jobj);
});
@@ -2659,8 +2661,10 @@ public Response getAuditFiles(@Context ContainerRequestContext crc,
missingFiles.forEach(mf -> {
String[] missingFile = mf.split(",");
NullSafeJsonBuilder jobj = NullSafeJsonBuilder.jsonObjectBuilder()
- .add("StorageIdentifier", missingFile[0])
- .add(missingFile[1], missingFile[2]);
+ .add("storageIdentifier", missingFile[0]);
+ for (int i = 2; i < missingFile.length; i+=2) {
+ jobj.add(missingFile[i-1], missingFile[i]);
+ }
jabMissingFiles.add(jobj);
});
job.add("missingFiles", jabMissingFiles);
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java b/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java
index 84011d7ac73..94aece95861 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/AdminIT.java
@@ -941,8 +941,8 @@ public void testFindMissingFiles() {
resp.prettyPrint();
resp.then().assertThat()
.statusCode(OK.getStatusCode())
- .body("data.failures[0].DatasetIdentifier", equalTo("bad/id"))
- .body("data.failures[0].Reason", equalTo("Not Found"));
+ .body("data.failures[0].datasetIdentifier", equalTo("bad/id"))
+ .body("data.failures[0].reason", equalTo("Not Found"));
}
private String createTestNonSuperuserApiToken() {
From 714b0f2ebd7f32131887fbfa6057701d10ced14e Mon Sep 17 00:00:00 2001
From: Florian Fritze
Date: Wed, 20 Nov 2024 07:33:11 +0100
Subject: [PATCH 174/270] removed entry from changelog.rst as requested
---
doc/sphinx-guides/source/api/changelog.rst | 4 ----
1 file changed, 4 deletions(-)
diff --git a/doc/sphinx-guides/source/api/changelog.rst b/doc/sphinx-guides/source/api/changelog.rst
index e76990f13c5..92cd4fc941b 100644
--- a/doc/sphinx-guides/source/api/changelog.rst
+++ b/doc/sphinx-guides/source/api/changelog.rst
@@ -7,10 +7,6 @@ This API changelog is experimental and we would love feedback on its usefulness.
:local:
:depth: 1
-v6.5
----
-- duplicated entries are corrected on the metadata page
-
v6.4
----
From 48d04e89de2f41de2b1941fb42a7c893ad22c701 Mon Sep 17 00:00:00 2001
From: julian-schneider <130765495+julian-schneider@users.noreply.github.com>
Date: Wed, 20 Nov 2024 12:48:19 +0100
Subject: [PATCH 175/270] Add PR number to
doc/release-notes/expose-export-formats.md
Co-authored-by: Philip Durbin
---
doc/release-notes/expose-export-formats.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/release-notes/expose-export-formats.md b/doc/release-notes/expose-export-formats.md
index 7e685b426fa..a21906d7bbb 100644
--- a/doc/release-notes/expose-export-formats.md
+++ b/doc/release-notes/expose-export-formats.md
@@ -1,2 +1,2 @@
# New API method for listing the available exporters
-Found at `/api/info/exportFormats`, produces an object with available format names as keys, and as values an object with various info about the exporter.
\ No newline at end of file
+Found at `/api/info/exportFormats`, produces an object with available format names as keys, and as values an object with various info about the exporter. See also #10739.
\ No newline at end of file
From f3b72c66fc89f8ec5fa7eb1f48fe187eabe7c8bf Mon Sep 17 00:00:00 2001
From: Julian Schneider
Date: Wed, 20 Nov 2024 15:56:19 +0100
Subject: [PATCH 176/270] Update expected JSON for api/info/exportFormats IT
Test
---
src/test/resources/json/export-formats.json | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/test/resources/json/export-formats.json b/src/test/resources/json/export-formats.json
index 0bca2314a8e..b4dc0168629 100644
--- a/src/test/resources/json/export-formats.json
+++ b/src/test/resources/json/export-formats.json
@@ -12,9 +12,9 @@
"mediaType": "application/xml",
"isHarvestable": true,
"isVisibleInUserInterface": true,
- "XMLNameSpace": "http://datacite.org/schema/kernel-3",
- "XMLSchemaLocation": "http://datacite.org/schema/kernel-3 http://schema.datacite.org/meta/kernel-3/metadata.xsd",
- "XMLSchemaVersion": "3.0"
+ "XMLNameSpace": "http://datacite.org/schema/kernel-4",
+ "XMLSchemaLocation": "http://datacite.org/schema/kernel-4 http://schema.datacite.org/meta/kernel-4.5/metadata.xsd",
+ "XMLSchemaVersion": "4.5"
},
"oai_dc": {
"displayName": "Dublin Core",
@@ -41,7 +41,7 @@
"isVisibleInUserInterface": true
},
"ddi": {
- "displayName": "DDI",
+ "displayName": "DDI Codebook v2",
"mediaType": "application/xml",
"isHarvestable": false,
"isVisibleInUserInterface": true,
@@ -71,7 +71,7 @@
"isVisibleInUserInterface": true
},
"oai_ddi": {
- "displayName": "DDI",
+ "displayName": "DDI Codebook v2",
"mediaType": "application/xml",
"isHarvestable": true,
"isVisibleInUserInterface": false,
From 26e85745f450a11f8e23e748fe0f0f05a647af76 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Wed, 20 Nov 2024 10:02:15 -0500
Subject: [PATCH 177/270] tabs to spaces
---
.../edu/harvard/iq/dataverse/api/Admin.java | 2132 ++++++++---------
1 file changed, 1066 insertions(+), 1066 deletions(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java
index 793e472ddac..61f76c9928c 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java
@@ -119,7 +119,7 @@
@Path("admin")
public class Admin extends AbstractApiBean {
- private static final Logger logger = Logger.getLogger(Admin.class.getName());
+ private static final Logger logger = Logger.getLogger(Admin.class.getName());
@EJB
AuthenticationProvidersRegistrationServiceBean authProvidersRegistrationSvc;
@@ -164,53 +164,53 @@ public class Admin extends AbstractApiBean {
@Inject
DataverseSession session;
- public static final String listUsersPartialAPIPath = "list-users";
- public static final String listUsersFullAPIPath = "/api/admin/" + listUsersPartialAPIPath;
-
- @Path("settings")
- @GET
- public Response listAllSettings() {
- JsonObjectBuilder bld = jsonObjectBuilder();
- settingsSvc.listAll().forEach(s -> bld.add(s.getName(), s.getContent()));
- return ok(bld);
- }
-
- @Path("settings/{name}")
- @PUT
- public Response putSetting(@PathParam("name") String name, String content) {
- Setting s = settingsSvc.set(name, content);
- return ok(jsonObjectBuilder().add(s.getName(), s.getContent()));
- }
-
- @Path("settings/{name}/lang/{lang}")
- @PUT
- public Response putSettingLang(@PathParam("name") String name, @PathParam("lang") String lang, String content) {
- Setting s = settingsSvc.set(name, lang, content);
- return ok("Setting " + name + " - " + lang + " - added.");
- }
-
- @Path("settings/{name}")
- @GET
- public Response getSetting(@PathParam("name") String name) {
- String s = settingsSvc.get(name);
-
- return (s != null) ? ok(s) : notFound("Setting " + name + " not found");
- }
-
- @Path("settings/{name}")
- @DELETE
- public Response deleteSetting(@PathParam("name") String name) {
- settingsSvc.delete(name);
-
- return ok("Setting " + name + " deleted.");
- }
-
- @Path("settings/{name}/lang/{lang}")
- @DELETE
- public Response deleteSettingLang(@PathParam("name") String name, @PathParam("lang") String lang) {
- settingsSvc.delete(name, lang);
- return ok("Setting " + name + " - " + lang + " deleted.");
- }
+ public static final String listUsersPartialAPIPath = "list-users";
+ public static final String listUsersFullAPIPath = "/api/admin/" + listUsersPartialAPIPath;
+
+ @Path("settings")
+ @GET
+ public Response listAllSettings() {
+ JsonObjectBuilder bld = jsonObjectBuilder();
+ settingsSvc.listAll().forEach(s -> bld.add(s.getName(), s.getContent()));
+ return ok(bld);
+ }
+
+ @Path("settings/{name}")
+ @PUT
+ public Response putSetting(@PathParam("name") String name, String content) {
+ Setting s = settingsSvc.set(name, content);
+ return ok(jsonObjectBuilder().add(s.getName(), s.getContent()));
+ }
+
+ @Path("settings/{name}/lang/{lang}")
+ @PUT
+ public Response putSettingLang(@PathParam("name") String name, @PathParam("lang") String lang, String content) {
+ Setting s = settingsSvc.set(name, lang, content);
+ return ok("Setting " + name + " - " + lang + " - added.");
+ }
+
+ @Path("settings/{name}")
+ @GET
+ public Response getSetting(@PathParam("name") String name) {
+ String s = settingsSvc.get(name);
+
+ return (s != null) ? ok(s) : notFound("Setting " + name + " not found");
+ }
+
+ @Path("settings/{name}")
+ @DELETE
+ public Response deleteSetting(@PathParam("name") String name) {
+ settingsSvc.delete(name);
+
+ return ok("Setting " + name + " deleted.");
+ }
+
+ @Path("settings/{name}/lang/{lang}")
+ @DELETE
+ public Response deleteSettingLang(@PathParam("name") String name, @PathParam("lang") String lang) {
+ settingsSvc.delete(name, lang);
+ return ok("Setting " + name + " - " + lang + " deleted.");
+ }
@Path("template/{id}")
@DELETE
@@ -281,130 +281,130 @@ public Response findTemplates(@PathParam("alias") String alias) {
}
- @Path("authenticationProviderFactories")
- @GET
- public Response listAuthProviderFactories() {
- return ok(authSvc.listProviderFactories().stream()
- .map(f -> jsonObjectBuilder().add("alias", f.getAlias()).add("info", f.getInfo()))
- .collect(toJsonArray()));
- }
-
- @Path("authenticationProviders")
- @GET
- public Response listAuthProviders() {
- return ok(em.createNamedQuery("AuthenticationProviderRow.findAll", AuthenticationProviderRow.class)
- .getResultList().stream().map(r -> json(r)).collect(toJsonArray()));
- }
-
- @Path("authenticationProviders")
- @POST
- public Response addProvider(AuthenticationProviderRow row) {
- try {
- AuthenticationProviderRow managed = em.find(AuthenticationProviderRow.class, row.getId());
- if (managed != null) {
- managed = em.merge(row);
- } else {
- em.persist(row);
- managed = row;
- }
- if (managed.isEnabled()) {
- AuthenticationProvider provider = authProvidersRegistrationSvc.loadProvider(managed);
- authProvidersRegistrationSvc.deregisterProvider(provider.getId());
- authProvidersRegistrationSvc.registerProvider(provider);
- }
- return created("/api/admin/authenticationProviders/" + managed.getId(), json(managed));
- } catch (AuthorizationSetupException e) {
- return error(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage());
- }
- }
-
- @Path("authenticationProviders/{id}")
- @GET
- public Response showProvider(@PathParam("id") String id) {
- AuthenticationProviderRow row = em.find(AuthenticationProviderRow.class, id);
- return (row != null) ? ok(json(row))
- : error(Status.NOT_FOUND, "Can't find authetication provider with id '" + id + "'");
- }
-
- @POST
- @Path("authenticationProviders/{id}/:enabled")
- public Response enableAuthenticationProvider_deprecated(@PathParam("id") String id, String body) {
- return enableAuthenticationProvider(id, body);
- }
-
- @PUT
- @Path("authenticationProviders/{id}/enabled")
- @Produces("application/json")
- public Response enableAuthenticationProvider(@PathParam("id") String id, String body) {
- body = body.trim();
- if (!Util.isBoolean(body)) {
- return error(Response.Status.BAD_REQUEST, "Illegal value '" + body + "'. Use 'true' or 'false'");
- }
- boolean enable = Util.isTrue(body);
-
- AuthenticationProviderRow row = em.find(AuthenticationProviderRow.class, id);
- if (row == null) {
- return notFound("Can't find authentication provider with id '" + id + "'");
- }
-
- row.setEnabled(enable);
- em.merge(row);
-
- if (enable) {
- // enable a provider
- if (authSvc.getAuthenticationProvider(id) != null) {
- return ok(String.format("Authentication provider '%s' already enabled", id));
- }
- try {
- authProvidersRegistrationSvc.registerProvider(authProvidersRegistrationSvc.loadProvider(row));
- return ok(String.format("Authentication Provider %s enabled", row.getId()));
-
- } catch (AuthenticationProviderFactoryNotFoundException ex) {
- return notFound(String.format("Can't instantiate provider, as there's no factory with alias %s",
- row.getFactoryAlias()));
- } catch (AuthorizationSetupException ex) {
- logger.log(Level.WARNING, "Error instantiating authentication provider: " + ex.getMessage(), ex);
- return error(Status.INTERNAL_SERVER_ERROR,
- String.format("Can't instantiate provider: %s", ex.getMessage()));
- }
-
- } else {
- // disable a provider
- authProvidersRegistrationSvc.deregisterProvider(id);
- return ok("Authentication Provider '" + id + "' disabled. "
- + (authSvc.getAuthenticationProviderIds().isEmpty()
- ? "WARNING: no enabled authentication providers left."
- : ""));
- }
- }
-
- @GET
- @Path("authenticationProviders/{id}/enabled")
- public Response checkAuthenticationProviderEnabled(@PathParam("id") String id) {
- List prvs = em
- .createNamedQuery("AuthenticationProviderRow.findById", AuthenticationProviderRow.class)
- .setParameter("id", id).getResultList();
- if (prvs.isEmpty()) {
- return notFound("Can't find a provider with id '" + id + "'.");
- } else {
- return ok(Boolean.toString(prvs.get(0).isEnabled()));
- }
- }
-
- @DELETE
- @Path("authenticationProviders/{id}/")
- public Response deleteAuthenticationProvider(@PathParam("id") String id) {
- authProvidersRegistrationSvc.deregisterProvider(id);
- AuthenticationProviderRow row = em.find(AuthenticationProviderRow.class, id);
- if (row != null) {
- em.remove(row);
- }
-
- return ok("AuthenticationProvider " + id + " deleted. "
- + (authSvc.getAuthenticationProviderIds().isEmpty()
- ? "WARNING: no enabled authentication providers left."
- : ""));
- }
+ @Path("authenticationProviderFactories")
+ @GET
+ public Response listAuthProviderFactories() {
+ return ok(authSvc.listProviderFactories().stream()
+ .map(f -> jsonObjectBuilder().add("alias", f.getAlias()).add("info", f.getInfo()))
+ .collect(toJsonArray()));
+ }
+
+ @Path("authenticationProviders")
+ @GET
+ public Response listAuthProviders() {
+ return ok(em.createNamedQuery("AuthenticationProviderRow.findAll", AuthenticationProviderRow.class)
+ .getResultList().stream().map(r -> json(r)).collect(toJsonArray()));
+ }
+
+ @Path("authenticationProviders")
+ @POST
+ public Response addProvider(AuthenticationProviderRow row) {
+ try {
+ AuthenticationProviderRow managed = em.find(AuthenticationProviderRow.class, row.getId());
+ if (managed != null) {
+ managed = em.merge(row);
+ } else {
+ em.persist(row);
+ managed = row;
+ }
+ if (managed.isEnabled()) {
+ AuthenticationProvider provider = authProvidersRegistrationSvc.loadProvider(managed);
+ authProvidersRegistrationSvc.deregisterProvider(provider.getId());
+ authProvidersRegistrationSvc.registerProvider(provider);
+ }
+ return created("/api/admin/authenticationProviders/" + managed.getId(), json(managed));
+ } catch (AuthorizationSetupException e) {
+ return error(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage());
+ }
+ }
+
+ @Path("authenticationProviders/{id}")
+ @GET
+ public Response showProvider(@PathParam("id") String id) {
+ AuthenticationProviderRow row = em.find(AuthenticationProviderRow.class, id);
+ return (row != null) ? ok(json(row))
+ : error(Status.NOT_FOUND, "Can't find authetication provider with id '" + id + "'");
+ }
+
+ @POST
+ @Path("authenticationProviders/{id}/:enabled")
+ public Response enableAuthenticationProvider_deprecated(@PathParam("id") String id, String body) {
+ return enableAuthenticationProvider(id, body);
+ }
+
+ @PUT
+ @Path("authenticationProviders/{id}/enabled")
+ @Produces("application/json")
+ public Response enableAuthenticationProvider(@PathParam("id") String id, String body) {
+ body = body.trim();
+ if (!Util.isBoolean(body)) {
+ return error(Response.Status.BAD_REQUEST, "Illegal value '" + body + "'. Use 'true' or 'false'");
+ }
+ boolean enable = Util.isTrue(body);
+
+ AuthenticationProviderRow row = em.find(AuthenticationProviderRow.class, id);
+ if (row == null) {
+ return notFound("Can't find authentication provider with id '" + id + "'");
+ }
+
+ row.setEnabled(enable);
+ em.merge(row);
+
+ if (enable) {
+ // enable a provider
+ if (authSvc.getAuthenticationProvider(id) != null) {
+ return ok(String.format("Authentication provider '%s' already enabled", id));
+ }
+ try {
+ authProvidersRegistrationSvc.registerProvider(authProvidersRegistrationSvc.loadProvider(row));
+ return ok(String.format("Authentication Provider %s enabled", row.getId()));
+
+ } catch (AuthenticationProviderFactoryNotFoundException ex) {
+ return notFound(String.format("Can't instantiate provider, as there's no factory with alias %s",
+ row.getFactoryAlias()));
+ } catch (AuthorizationSetupException ex) {
+ logger.log(Level.WARNING, "Error instantiating authentication provider: " + ex.getMessage(), ex);
+ return error(Status.INTERNAL_SERVER_ERROR,
+ String.format("Can't instantiate provider: %s", ex.getMessage()));
+ }
+
+ } else {
+ // disable a provider
+ authProvidersRegistrationSvc.deregisterProvider(id);
+ return ok("Authentication Provider '" + id + "' disabled. "
+ + (authSvc.getAuthenticationProviderIds().isEmpty()
+ ? "WARNING: no enabled authentication providers left."
+ : ""));
+ }
+ }
+
+ @GET
+ @Path("authenticationProviders/{id}/enabled")
+ public Response checkAuthenticationProviderEnabled(@PathParam("id") String id) {
+ List prvs = em
+ .createNamedQuery("AuthenticationProviderRow.findById", AuthenticationProviderRow.class)
+ .setParameter("id", id).getResultList();
+ if (prvs.isEmpty()) {
+ return notFound("Can't find a provider with id '" + id + "'.");
+ } else {
+ return ok(Boolean.toString(prvs.get(0).isEnabled()));
+ }
+ }
+
+ @DELETE
+ @Path("authenticationProviders/{id}/")
+ public Response deleteAuthenticationProvider(@PathParam("id") String id) {
+ authProvidersRegistrationSvc.deregisterProvider(id);
+ AuthenticationProviderRow row = em.find(AuthenticationProviderRow.class, id);
+ if (row != null) {
+ em.remove(row);
+ }
+
+ return ok("AuthenticationProvider " + id + " deleted. "
+ + (authSvc.getAuthenticationProviderIds().isEmpty()
+ ? "WARNING: no enabled authentication providers left."
+ : ""));
+ }
@GET
@Path("authenticatedUsers/{identifier}/")
@@ -489,520 +489,520 @@ private Response deactivateAuthenticatedUser(AuthenticatedUser userToDisable) {
}
}
- @POST
- @Path("publishDataverseAsCreator/{id}")
- public Response publishDataverseAsCreator(@PathParam("id") long id) {
- try {
- Dataverse dataverse = dataverseSvc.find(id);
- if (dataverse != null) {
- AuthenticatedUser authenticatedUser = dataverse.getCreator();
- return ok(json(execCommand(
- new PublishDataverseCommand(createDataverseRequest(authenticatedUser), dataverse))));
- } else {
- return error(Status.BAD_REQUEST, "Could not find dataverse with id " + id);
- }
- } catch (WrappedResponse wr) {
- return wr.getResponse();
- }
- }
-
- @Deprecated
- @GET
- @AuthRequired
- @Path("authenticatedUsers")
- public Response listAuthenticatedUsers(@Context ContainerRequestContext crc) {
- try {
- AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
- if (!user.isSuperuser()) {
- return error(Response.Status.FORBIDDEN, "Superusers only.");
- }
- } catch (WrappedResponse ex) {
- return error(Response.Status.FORBIDDEN, "Superusers only.");
- }
- JsonArrayBuilder userArray = Json.createArrayBuilder();
- authSvc.findAllAuthenticatedUsers().stream().forEach((user) -> {
- userArray.add(json(user));
- });
- return ok(userArray);
- }
-
- @GET
- @AuthRequired
- @Path(listUsersPartialAPIPath)
- @Produces({ "application/json" })
- public Response filterAuthenticatedUsers(
- @Context ContainerRequestContext crc,
- @QueryParam("searchTerm") String searchTerm,
- @QueryParam("selectedPage") Integer selectedPage,
- @QueryParam("itemsPerPage") Integer itemsPerPage,
- @QueryParam("sortKey") String sortKey
- ) {
-
- User authUser = getRequestUser(crc);
-
- if (!authUser.isSuperuser()) {
- return error(Response.Status.FORBIDDEN,
- BundleUtil.getStringFromBundle("dashboard.list_users.api.auth.not_superuser"));
- }
-
- UserListMaker userListMaker = new UserListMaker(userService);
-
- // String sortKey = null;
- UserListResult userListResult = userListMaker.runUserSearch(searchTerm, itemsPerPage, selectedPage, sortKey);
-
- return ok(userListResult.toJSON());
- }
-
- /**
- * @todo Make this support creation of BuiltInUsers.
- *
- * @todo Add way more error checking. Only the happy path is tested by AdminIT.
- */
- @POST
- @Path("authenticatedUsers")
- public Response createAuthenicatedUser(JsonObject jsonObject) {
- logger.fine("JSON in: " + jsonObject);
- String persistentUserId = jsonObject.getString("persistentUserId");
- String identifier = jsonObject.getString("identifier");
- String proposedAuthenticatedUserIdentifier = identifier.replaceFirst("@", "");
- String firstName = jsonObject.getString("firstName");
- String lastName = jsonObject.getString("lastName");
- String emailAddress = jsonObject.getString("email");
- String position = null;
- String affiliation = null;
- UserRecordIdentifier userRecordId = new UserRecordIdentifier(jsonObject.getString("authenticationProviderId"),
- persistentUserId);
- AuthenticatedUserDisplayInfo userDisplayInfo = new AuthenticatedUserDisplayInfo(firstName, lastName,
- emailAddress, affiliation, position);
- boolean generateUniqueIdentifier = true;
- AuthenticatedUser authenticatedUser = authSvc.createAuthenticatedUser(userRecordId,
- proposedAuthenticatedUserIdentifier, userDisplayInfo, true);
- return ok(json(authenticatedUser));
- }
+ @POST
+ @Path("publishDataverseAsCreator/{id}")
+ public Response publishDataverseAsCreator(@PathParam("id") long id) {
+ try {
+ Dataverse dataverse = dataverseSvc.find(id);
+ if (dataverse != null) {
+ AuthenticatedUser authenticatedUser = dataverse.getCreator();
+ return ok(json(execCommand(
+ new PublishDataverseCommand(createDataverseRequest(authenticatedUser), dataverse))));
+ } else {
+ return error(Status.BAD_REQUEST, "Could not find dataverse with id " + id);
+ }
+ } catch (WrappedResponse wr) {
+ return wr.getResponse();
+ }
+ }
+
+ @Deprecated
+ @GET
+ @AuthRequired
+ @Path("authenticatedUsers")
+ public Response listAuthenticatedUsers(@Context ContainerRequestContext crc) {
+ try {
+ AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
+ if (!user.isSuperuser()) {
+ return error(Response.Status.FORBIDDEN, "Superusers only.");
+ }
+ } catch (WrappedResponse ex) {
+ return error(Response.Status.FORBIDDEN, "Superusers only.");
+ }
+ JsonArrayBuilder userArray = Json.createArrayBuilder();
+ authSvc.findAllAuthenticatedUsers().stream().forEach((user) -> {
+ userArray.add(json(user));
+ });
+ return ok(userArray);
+ }
+
+ @GET
+ @AuthRequired
+ @Path(listUsersPartialAPIPath)
+ @Produces({ "application/json" })
+ public Response filterAuthenticatedUsers(
+ @Context ContainerRequestContext crc,
+ @QueryParam("searchTerm") String searchTerm,
+ @QueryParam("selectedPage") Integer selectedPage,
+ @QueryParam("itemsPerPage") Integer itemsPerPage,
+ @QueryParam("sortKey") String sortKey
+ ) {
+
+ User authUser = getRequestUser(crc);
+
+ if (!authUser.isSuperuser()) {
+ return error(Response.Status.FORBIDDEN,
+ BundleUtil.getStringFromBundle("dashboard.list_users.api.auth.not_superuser"));
+ }
+
+ UserListMaker userListMaker = new UserListMaker(userService);
+
+ // String sortKey = null;
+ UserListResult userListResult = userListMaker.runUserSearch(searchTerm, itemsPerPage, selectedPage, sortKey);
+
+ return ok(userListResult.toJSON());
+ }
+
+ /**
+ * @todo Make this support creation of BuiltInUsers.
+ *
+ * @todo Add way more error checking. Only the happy path is tested by AdminIT.
+ */
+ @POST
+ @Path("authenticatedUsers")
+ public Response createAuthenicatedUser(JsonObject jsonObject) {
+ logger.fine("JSON in: " + jsonObject);
+ String persistentUserId = jsonObject.getString("persistentUserId");
+ String identifier = jsonObject.getString("identifier");
+ String proposedAuthenticatedUserIdentifier = identifier.replaceFirst("@", "");
+ String firstName = jsonObject.getString("firstName");
+ String lastName = jsonObject.getString("lastName");
+ String emailAddress = jsonObject.getString("email");
+ String position = null;
+ String affiliation = null;
+ UserRecordIdentifier userRecordId = new UserRecordIdentifier(jsonObject.getString("authenticationProviderId"),
+ persistentUserId);
+ AuthenticatedUserDisplayInfo userDisplayInfo = new AuthenticatedUserDisplayInfo(firstName, lastName,
+ emailAddress, affiliation, position);
+ boolean generateUniqueIdentifier = true;
+ AuthenticatedUser authenticatedUser = authSvc.createAuthenticatedUser(userRecordId,
+ proposedAuthenticatedUserIdentifier, userDisplayInfo, true);
+ return ok(json(authenticatedUser));
+ }
//TODO: Delete this endpoint after 4.9.3. Was updated with change in docs. --MAD
- /**
- * curl -X PUT -d "shib@mailinator.com"
- * http://localhost:8080/api/admin/authenticatedUsers/id/11/convertShibToBuiltIn
- *
- * @deprecated We have documented this API endpoint so we'll keep in around for
- * a while but we should encourage everyone to switch to the
- * "convertRemoteToBuiltIn" endpoint and then remove this
- * Shib-specfic one.
- */
- @PUT
- @AuthRequired
- @Path("authenticatedUsers/id/{id}/convertShibToBuiltIn")
- @Deprecated
- public Response convertShibUserToBuiltin(@Context ContainerRequestContext crc, @PathParam("id") Long id, String newEmailAddress) {
- try {
- AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
- if (!user.isSuperuser()) {
- return error(Response.Status.FORBIDDEN, "Superusers only.");
- }
- } catch (WrappedResponse ex) {
- return error(Response.Status.FORBIDDEN, "Superusers only.");
- }
- try {
- BuiltinUser builtinUser = authSvc.convertRemoteToBuiltIn(id, newEmailAddress);
- if (builtinUser == null) {
- return error(Response.Status.BAD_REQUEST, "User id " + id
- + " could not be converted from Shibboleth to BuiltIn. An Exception was not thrown.");
- }
+ /**
+ * curl -X PUT -d "shib@mailinator.com"
+ * http://localhost:8080/api/admin/authenticatedUsers/id/11/convertShibToBuiltIn
+ *
+ * @deprecated We have documented this API endpoint so we'll keep in around for
+ * a while but we should encourage everyone to switch to the
+ * "convertRemoteToBuiltIn" endpoint and then remove this
+ * Shib-specfic one.
+ */
+ @PUT
+ @AuthRequired
+ @Path("authenticatedUsers/id/{id}/convertShibToBuiltIn")
+ @Deprecated
+ public Response convertShibUserToBuiltin(@Context ContainerRequestContext crc, @PathParam("id") Long id, String newEmailAddress) {
+ try {
+ AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
+ if (!user.isSuperuser()) {
+ return error(Response.Status.FORBIDDEN, "Superusers only.");
+ }
+ } catch (WrappedResponse ex) {
+ return error(Response.Status.FORBIDDEN, "Superusers only.");
+ }
+ try {
+ BuiltinUser builtinUser = authSvc.convertRemoteToBuiltIn(id, newEmailAddress);
+ if (builtinUser == null) {
+ return error(Response.Status.BAD_REQUEST, "User id " + id
+ + " could not be converted from Shibboleth to BuiltIn. An Exception was not thrown.");
+ }
AuthenticatedUser authUser = authSvc.getAuthenticatedUser(builtinUser.getUserName());
- JsonObjectBuilder output = Json.createObjectBuilder();
- output.add("email", authUser.getEmail());
- output.add("username", builtinUser.getUserName());
- return ok(output);
- } catch (Throwable ex) {
- StringBuilder sb = new StringBuilder();
- sb.append(ex + " ");
- while (ex.getCause() != null) {
- ex = ex.getCause();
- sb.append(ex + " ");
- }
- String msg = "User id " + id
- + " could not be converted from Shibboleth to BuiltIn. Details from Exception: " + sb;
- logger.info(msg);
- return error(Response.Status.BAD_REQUEST, msg);
- }
- }
-
- @PUT
- @AuthRequired
- @Path("authenticatedUsers/id/{id}/convertRemoteToBuiltIn")
- public Response convertOAuthUserToBuiltin(@Context ContainerRequestContext crc, @PathParam("id") Long id, String newEmailAddress) {
- try {
- AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
- if (!user.isSuperuser()) {
- return error(Response.Status.FORBIDDEN, "Superusers only.");
- }
- } catch (WrappedResponse ex) {
- return error(Response.Status.FORBIDDEN, "Superusers only.");
- }
- try {
- BuiltinUser builtinUser = authSvc.convertRemoteToBuiltIn(id, newEmailAddress);
+ JsonObjectBuilder output = Json.createObjectBuilder();
+ output.add("email", authUser.getEmail());
+ output.add("username", builtinUser.getUserName());
+ return ok(output);
+ } catch (Throwable ex) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(ex + " ");
+ while (ex.getCause() != null) {
+ ex = ex.getCause();
+ sb.append(ex + " ");
+ }
+ String msg = "User id " + id
+ + " could not be converted from Shibboleth to BuiltIn. Details from Exception: " + sb;
+ logger.info(msg);
+ return error(Response.Status.BAD_REQUEST, msg);
+ }
+ }
+
+ @PUT
+ @AuthRequired
+ @Path("authenticatedUsers/id/{id}/convertRemoteToBuiltIn")
+ public Response convertOAuthUserToBuiltin(@Context ContainerRequestContext crc, @PathParam("id") Long id, String newEmailAddress) {
+ try {
+ AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
+ if (!user.isSuperuser()) {
+ return error(Response.Status.FORBIDDEN, "Superusers only.");
+ }
+ } catch (WrappedResponse ex) {
+ return error(Response.Status.FORBIDDEN, "Superusers only.");
+ }
+ try {
+ BuiltinUser builtinUser = authSvc.convertRemoteToBuiltIn(id, newEmailAddress);
//AuthenticatedUser authUser = authService.getAuthenticatedUser(aUser.getUserName());
- if (builtinUser == null) {
- return error(Response.Status.BAD_REQUEST, "User id " + id
- + " could not be converted from remote to BuiltIn. An Exception was not thrown.");
- }
+ if (builtinUser == null) {
+ return error(Response.Status.BAD_REQUEST, "User id " + id
+ + " could not be converted from remote to BuiltIn. An Exception was not thrown.");
+ }
AuthenticatedUser authUser = authSvc.getAuthenticatedUser(builtinUser.getUserName());
- JsonObjectBuilder output = Json.createObjectBuilder();
- output.add("email", authUser.getEmail());
- output.add("username", builtinUser.getUserName());
- return ok(output);
- } catch (Throwable ex) {
- StringBuilder sb = new StringBuilder();
- sb.append(ex + " ");
- while (ex.getCause() != null) {
- ex = ex.getCause();
- sb.append(ex + " ");
- }
- String msg = "User id " + id + " could not be converted from remote to BuiltIn. Details from Exception: "
- + sb;
- logger.info(msg);
- return error(Response.Status.BAD_REQUEST, msg);
- }
- }
-
- /**
- * This is used in testing via AdminIT.java but we don't expect sysadmins to use
- * this.
- */
- @PUT
- @AuthRequired
- @Path("authenticatedUsers/convert/builtin2shib")
- public Response builtin2shib(@Context ContainerRequestContext crc, String content) {
- logger.info("entering builtin2shib...");
- try {
- AuthenticatedUser userToRunThisMethod = getRequestAuthenticatedUserOrDie(crc);
- if (!userToRunThisMethod.isSuperuser()) {
- return error(Response.Status.FORBIDDEN, "Superusers only.");
- }
- } catch (WrappedResponse ex) {
- return error(Response.Status.FORBIDDEN, "Superusers only.");
- }
- boolean disabled = false;
- if (disabled) {
- return error(Response.Status.BAD_REQUEST, "API endpoint disabled.");
- }
- AuthenticatedUser builtInUserToConvert = null;
- String emailToFind;
- String password;
- String authuserId = "0"; // could let people specify id on authuser table. probably better to let them
- // tell us their
- String newEmailAddressToUse;
- try {
- String[] args = content.split(":");
- emailToFind = args[0];
- password = args[1];
- newEmailAddressToUse = args[2];
- // authuserId = args[666];
- } catch (ArrayIndexOutOfBoundsException ex) {
- return error(Response.Status.BAD_REQUEST, "Problem with content <<<" + content + ">>>: " + ex.toString());
- }
- AuthenticatedUser existingAuthUserFoundByEmail = shibService.findAuthUserByEmail(emailToFind);
- String existing = "NOT FOUND";
- if (existingAuthUserFoundByEmail != null) {
- builtInUserToConvert = existingAuthUserFoundByEmail;
- existing = existingAuthUserFoundByEmail.getIdentifier();
- } else {
- long longToLookup = Long.parseLong(authuserId);
- AuthenticatedUser specifiedUserToConvert = authSvc.findByID(longToLookup);
- if (specifiedUserToConvert != null) {
- builtInUserToConvert = specifiedUserToConvert;
- } else {
- return error(Response.Status.BAD_REQUEST,
- "No user to convert. We couldn't find a *single* existing user account based on " + emailToFind
- + " and no user was found using specified id " + longToLookup);
- }
- }
- String shibProviderId = ShibAuthenticationProvider.PROVIDER_ID;
- Map randomUser = authTestDataService.getRandomUser();
- // String eppn = UUID.randomUUID().toString().substring(0, 8);
- String eppn = randomUser.get("eppn");
- String idPEntityId = randomUser.get("idp");
- String notUsed = null;
- String separator = "|";
- UserIdentifier newUserIdentifierInLookupTable = new UserIdentifier(idPEntityId + separator + eppn, notUsed);
- String overwriteFirstName = randomUser.get("firstName");
- String overwriteLastName = randomUser.get("lastName");
- String overwriteEmail = randomUser.get("email");
- overwriteEmail = newEmailAddressToUse;
- logger.info("overwriteEmail: " + overwriteEmail);
- boolean validEmail = EMailValidator.isEmailValid(overwriteEmail);
- if (!validEmail) {
- // See https://github.com/IQSS/dataverse/issues/2998
- return error(Response.Status.BAD_REQUEST, "invalid email: " + overwriteEmail);
- }
- /**
- * @todo If affiliation is not null, put it in RoleAssigneeDisplayInfo
- * constructor.
- */
- /**
- * Here we are exercising (via an API test) shibService.getAffiliation with the
- * TestShib IdP and a non-production DevShibAccountType.
- */
- idPEntityId = ShibUtil.testShibIdpEntityId;
- String overwriteAffiliation = shibService.getAffiliation(idPEntityId,
- ShibServiceBean.DevShibAccountType.RANDOM);
- logger.info("overwriteAffiliation: " + overwriteAffiliation);
- /**
- * @todo Find a place to put "position" in the authenticateduser table:
- * https://github.com/IQSS/dataverse/issues/1444#issuecomment-74134694
- */
- String overwritePosition = "staff;student";
- AuthenticatedUserDisplayInfo displayInfo = new AuthenticatedUserDisplayInfo(overwriteFirstName,
- overwriteLastName, overwriteEmail, overwriteAffiliation, overwritePosition);
- JsonObjectBuilder response = Json.createObjectBuilder();
- JsonArrayBuilder problems = Json.createArrayBuilder();
- if (password != null) {
- response.add("password supplied", password);
- boolean knowsExistingPassword = false;
- BuiltinUser oldBuiltInUser = builtinUserService.findByUserName(builtInUserToConvert.getUserIdentifier());
- if (oldBuiltInUser != null) {
+ JsonObjectBuilder output = Json.createObjectBuilder();
+ output.add("email", authUser.getEmail());
+ output.add("username", builtinUser.getUserName());
+ return ok(output);
+ } catch (Throwable ex) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(ex + " ");
+ while (ex.getCause() != null) {
+ ex = ex.getCause();
+ sb.append(ex + " ");
+ }
+ String msg = "User id " + id + " could not be converted from remote to BuiltIn. Details from Exception: "
+ + sb;
+ logger.info(msg);
+ return error(Response.Status.BAD_REQUEST, msg);
+ }
+ }
+
+ /**
+ * This is used in testing via AdminIT.java but we don't expect sysadmins to use
+ * this.
+ */
+ @PUT
+ @AuthRequired
+ @Path("authenticatedUsers/convert/builtin2shib")
+ public Response builtin2shib(@Context ContainerRequestContext crc, String content) {
+ logger.info("entering builtin2shib...");
+ try {
+ AuthenticatedUser userToRunThisMethod = getRequestAuthenticatedUserOrDie(crc);
+ if (!userToRunThisMethod.isSuperuser()) {
+ return error(Response.Status.FORBIDDEN, "Superusers only.");
+ }
+ } catch (WrappedResponse ex) {
+ return error(Response.Status.FORBIDDEN, "Superusers only.");
+ }
+ boolean disabled = false;
+ if (disabled) {
+ return error(Response.Status.BAD_REQUEST, "API endpoint disabled.");
+ }
+ AuthenticatedUser builtInUserToConvert = null;
+ String emailToFind;
+ String password;
+ String authuserId = "0"; // could let people specify id on authuser table. probably better to let them
+ // tell us their
+ String newEmailAddressToUse;
+ try {
+ String[] args = content.split(":");
+ emailToFind = args[0];
+ password = args[1];
+ newEmailAddressToUse = args[2];
+ // authuserId = args[666];
+ } catch (ArrayIndexOutOfBoundsException ex) {
+ return error(Response.Status.BAD_REQUEST, "Problem with content <<<" + content + ">>>: " + ex.toString());
+ }
+ AuthenticatedUser existingAuthUserFoundByEmail = shibService.findAuthUserByEmail(emailToFind);
+ String existing = "NOT FOUND";
+ if (existingAuthUserFoundByEmail != null) {
+ builtInUserToConvert = existingAuthUserFoundByEmail;
+ existing = existingAuthUserFoundByEmail.getIdentifier();
+ } else {
+ long longToLookup = Long.parseLong(authuserId);
+ AuthenticatedUser specifiedUserToConvert = authSvc.findByID(longToLookup);
+ if (specifiedUserToConvert != null) {
+ builtInUserToConvert = specifiedUserToConvert;
+ } else {
+ return error(Response.Status.BAD_REQUEST,
+ "No user to convert. We couldn't find a *single* existing user account based on " + emailToFind
+ + " and no user was found using specified id " + longToLookup);
+ }
+ }
+ String shibProviderId = ShibAuthenticationProvider.PROVIDER_ID;
+ Map randomUser = authTestDataService.getRandomUser();
+ // String eppn = UUID.randomUUID().toString().substring(0, 8);
+ String eppn = randomUser.get("eppn");
+ String idPEntityId = randomUser.get("idp");
+ String notUsed = null;
+ String separator = "|";
+ UserIdentifier newUserIdentifierInLookupTable = new UserIdentifier(idPEntityId + separator + eppn, notUsed);
+ String overwriteFirstName = randomUser.get("firstName");
+ String overwriteLastName = randomUser.get("lastName");
+ String overwriteEmail = randomUser.get("email");
+ overwriteEmail = newEmailAddressToUse;
+ logger.info("overwriteEmail: " + overwriteEmail);
+ boolean validEmail = EMailValidator.isEmailValid(overwriteEmail);
+ if (!validEmail) {
+ // See https://github.com/IQSS/dataverse/issues/2998
+ return error(Response.Status.BAD_REQUEST, "invalid email: " + overwriteEmail);
+ }
+ /**
+ * @todo If affiliation is not null, put it in RoleAssigneeDisplayInfo
+ * constructor.
+ */
+ /**
+ * Here we are exercising (via an API test) shibService.getAffiliation with the
+ * TestShib IdP and a non-production DevShibAccountType.
+ */
+ idPEntityId = ShibUtil.testShibIdpEntityId;
+ String overwriteAffiliation = shibService.getAffiliation(idPEntityId,
+ ShibServiceBean.DevShibAccountType.RANDOM);
+ logger.info("overwriteAffiliation: " + overwriteAffiliation);
+ /**
+ * @todo Find a place to put "position" in the authenticateduser table:
+ * https://github.com/IQSS/dataverse/issues/1444#issuecomment-74134694
+ */
+ String overwritePosition = "staff;student";
+ AuthenticatedUserDisplayInfo displayInfo = new AuthenticatedUserDisplayInfo(overwriteFirstName,
+ overwriteLastName, overwriteEmail, overwriteAffiliation, overwritePosition);
+ JsonObjectBuilder response = Json.createObjectBuilder();
+ JsonArrayBuilder problems = Json.createArrayBuilder();
+ if (password != null) {
+ response.add("password supplied", password);
+ boolean knowsExistingPassword = false;
+ BuiltinUser oldBuiltInUser = builtinUserService.findByUserName(builtInUserToConvert.getUserIdentifier());
+ if (oldBuiltInUser != null) {
if (builtInUserToConvert.isDeactivated()) {
problems.add("builtin account has been deactivated");
return error(Status.BAD_REQUEST, problems.build().toString());
}
- String usernameOfBuiltinAccountToConvert = oldBuiltInUser.getUserName();
- response.add("old username", usernameOfBuiltinAccountToConvert);
- AuthenticatedUser authenticatedUser = authSvc.canLogInAsBuiltinUser(usernameOfBuiltinAccountToConvert,
- password);
- if (authenticatedUser != null) {
- knowsExistingPassword = true;
- AuthenticatedUser convertedUser = authSvc.convertBuiltInToShib(builtInUserToConvert, shibProviderId,
- newUserIdentifierInLookupTable);
- if (convertedUser != null) {
- /**
- * @todo Display name is not being overwritten. Logic must be in Shib backing
- * bean
- */
- AuthenticatedUser updatedInfoUser = authSvc.updateAuthenticatedUser(convertedUser, displayInfo);
- if (updatedInfoUser != null) {
- response.add("display name overwritten with", updatedInfoUser.getName());
- } else {
- problems.add("couldn't update display info");
- }
- } else {
- problems.add("unable to convert user");
- }
- }
- } else {
- problems.add("couldn't find old username");
- }
- if (!knowsExistingPassword) {
- String message = "User doesn't know password.";
- problems.add(message);
- /**
- * @todo Someday we should make a errorResponse method that takes JSON arrays
- * and objects.
- */
- return error(Status.BAD_REQUEST, problems.build().toString());
- }
- // response.add("knows existing password", knowsExistingPassword);
- }
-
- response.add("user to convert", builtInUserToConvert.getIdentifier());
- response.add("existing user found by email (prompt to convert)", existing);
- response.add("changing to this provider", shibProviderId);
- response.add("value to overwrite old first name", overwriteFirstName);
- response.add("value to overwrite old last name", overwriteLastName);
- response.add("value to overwrite old email address", overwriteEmail);
- if (overwriteAffiliation != null) {
- response.add("affiliation", overwriteAffiliation);
- }
- response.add("problems", problems);
- return ok(response);
- }
-
- /**
- * This is used in testing via AdminIT.java but we don't expect sysadmins to use
- * this.
- */
- @PUT
- @AuthRequired
- @Path("authenticatedUsers/convert/builtin2oauth")
- public Response builtin2oauth(@Context ContainerRequestContext crc, String content) {
- logger.info("entering builtin2oauth...");
- try {
- AuthenticatedUser userToRunThisMethod = getRequestAuthenticatedUserOrDie(crc);
- if (!userToRunThisMethod.isSuperuser()) {
- return error(Response.Status.FORBIDDEN, "Superusers only.");
- }
- } catch (WrappedResponse ex) {
- return error(Response.Status.FORBIDDEN, "Superusers only.");
- }
- boolean disabled = false;
- if (disabled) {
- return error(Response.Status.BAD_REQUEST, "API endpoint disabled.");
- }
- AuthenticatedUser builtInUserToConvert = null;
- String emailToFind;
- String password;
- String authuserId = "0"; // could let people specify id on authuser table. probably better to let them
- // tell us their
- String newEmailAddressToUse;
- String newProviderId;
- String newPersistentUserIdInLookupTable;
- logger.info("content: " + content);
- try {
- String[] args = content.split(":");
- emailToFind = args[0];
- password = args[1];
- newEmailAddressToUse = args[2];
- newProviderId = args[3];
- newPersistentUserIdInLookupTable = args[4];
- // authuserId = args[666];
- } catch (ArrayIndexOutOfBoundsException ex) {
- return error(Response.Status.BAD_REQUEST, "Problem with content <<<" + content + ">>>: " + ex.toString());
- }
- AuthenticatedUser existingAuthUserFoundByEmail = shibService.findAuthUserByEmail(emailToFind);
- String existing = "NOT FOUND";
- if (existingAuthUserFoundByEmail != null) {
- builtInUserToConvert = existingAuthUserFoundByEmail;
- existing = existingAuthUserFoundByEmail.getIdentifier();
- } else {
- long longToLookup = Long.parseLong(authuserId);
- AuthenticatedUser specifiedUserToConvert = authSvc.findByID(longToLookup);
- if (specifiedUserToConvert != null) {
- builtInUserToConvert = specifiedUserToConvert;
- } else {
- return error(Response.Status.BAD_REQUEST,
- "No user to convert. We couldn't find a *single* existing user account based on " + emailToFind
- + " and no user was found using specified id " + longToLookup);
- }
- }
- // String shibProviderId = ShibAuthenticationProvider.PROVIDER_ID;
- Map randomUser = authTestDataService.getRandomUser();
- // String eppn = UUID.randomUUID().toString().substring(0, 8);
- String eppn = randomUser.get("eppn");
- String idPEntityId = randomUser.get("idp");
- String notUsed = null;
- String separator = "|";
- // UserIdentifier newUserIdentifierInLookupTable = new
- // UserIdentifier(idPEntityId + separator + eppn, notUsed);
- UserIdentifier newUserIdentifierInLookupTable = new UserIdentifier(newPersistentUserIdInLookupTable, notUsed);
- String overwriteFirstName = randomUser.get("firstName");
- String overwriteLastName = randomUser.get("lastName");
- String overwriteEmail = randomUser.get("email");
- overwriteEmail = newEmailAddressToUse;
- logger.info("overwriteEmail: " + overwriteEmail);
- boolean validEmail = EMailValidator.isEmailValid(overwriteEmail);
- if (!validEmail) {
- // See https://github.com/IQSS/dataverse/issues/2998
- return error(Response.Status.BAD_REQUEST, "invalid email: " + overwriteEmail);
- }
- /**
- * @todo If affiliation is not null, put it in RoleAssigneeDisplayInfo
- * constructor.
- */
- /**
- * Here we are exercising (via an API test) shibService.getAffiliation with the
- * TestShib IdP and a non-production DevShibAccountType.
- */
- // idPEntityId = ShibUtil.testShibIdpEntityId;
- // String overwriteAffiliation = shibService.getAffiliation(idPEntityId,
- // ShibServiceBean.DevShibAccountType.RANDOM);
- String overwriteAffiliation = null;
- logger.info("overwriteAffiliation: " + overwriteAffiliation);
- /**
- * @todo Find a place to put "position" in the authenticateduser table:
- * https://github.com/IQSS/dataverse/issues/1444#issuecomment-74134694
- */
- String overwritePosition = "staff;student";
- AuthenticatedUserDisplayInfo displayInfo = new AuthenticatedUserDisplayInfo(overwriteFirstName,
- overwriteLastName, overwriteEmail, overwriteAffiliation, overwritePosition);
- JsonObjectBuilder response = Json.createObjectBuilder();
- JsonArrayBuilder problems = Json.createArrayBuilder();
- if (password != null) {
- response.add("password supplied", password);
- boolean knowsExistingPassword = false;
- BuiltinUser oldBuiltInUser = builtinUserService.findByUserName(builtInUserToConvert.getUserIdentifier());
- if (oldBuiltInUser != null) {
- String usernameOfBuiltinAccountToConvert = oldBuiltInUser.getUserName();
- response.add("old username", usernameOfBuiltinAccountToConvert);
- AuthenticatedUser authenticatedUser = authSvc.canLogInAsBuiltinUser(usernameOfBuiltinAccountToConvert,
- password);
- if (authenticatedUser != null) {
- knowsExistingPassword = true;
- AuthenticatedUser convertedUser = authSvc.convertBuiltInUserToRemoteUser(builtInUserToConvert,
- newProviderId, newUserIdentifierInLookupTable);
- if (convertedUser != null) {
- /**
- * @todo Display name is not being overwritten. Logic must be in Shib backing
- * bean
- */
- AuthenticatedUser updatedInfoUser = authSvc.updateAuthenticatedUser(convertedUser, displayInfo);
- if (updatedInfoUser != null) {
- response.add("display name overwritten with", updatedInfoUser.getName());
- } else {
- problems.add("couldn't update display info");
- }
- } else {
- problems.add("unable to convert user");
- }
- }
- } else {
- problems.add("couldn't find old username");
- }
- if (!knowsExistingPassword) {
- String message = "User doesn't know password.";
- problems.add(message);
- /**
- * @todo Someday we should make a errorResponse method that takes JSON arrays
- * and objects.
- */
- return error(Status.BAD_REQUEST, problems.build().toString());
- }
- // response.add("knows existing password", knowsExistingPassword);
- }
-
- response.add("user to convert", builtInUserToConvert.getIdentifier());
- response.add("existing user found by email (prompt to convert)", existing);
- response.add("changing to this provider", newProviderId);
- response.add("value to overwrite old first name", overwriteFirstName);
- response.add("value to overwrite old last name", overwriteLastName);
- response.add("value to overwrite old email address", overwriteEmail);
- if (overwriteAffiliation != null) {
- response.add("affiliation", overwriteAffiliation);
- }
- response.add("problems", problems);
- return ok(response);
- }
-
-
-
-
- @Path("roles")
- @POST
- public Response createNewBuiltinRole(RoleDTO roleDto) {
- ActionLogRecord alr = new ActionLogRecord(ActionLogRecord.ActionType.Admin, "createBuiltInRole")
- .setInfo(roleDto.getAlias() + ":" + roleDto.getDescription());
- try {
- return ok(json(rolesSvc.save(roleDto.asRole())));
- } catch (Exception e) {
- alr.setActionResult(ActionLogRecord.Result.InternalError);
- alr.setInfo(alr.getInfo() + "// " + e.getMessage());
- return error(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage());
- } finally {
- actionLogSvc.log(alr);
- }
- }
-
- @Path("roles")
- @GET
- public Response listBuiltinRoles() {
- try {
- return ok(rolesToJson(rolesSvc.findBuiltinRoles()));
- } catch (Exception e) {
- return error(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage());
- }
- }
+ String usernameOfBuiltinAccountToConvert = oldBuiltInUser.getUserName();
+ response.add("old username", usernameOfBuiltinAccountToConvert);
+ AuthenticatedUser authenticatedUser = authSvc.canLogInAsBuiltinUser(usernameOfBuiltinAccountToConvert,
+ password);
+ if (authenticatedUser != null) {
+ knowsExistingPassword = true;
+ AuthenticatedUser convertedUser = authSvc.convertBuiltInToShib(builtInUserToConvert, shibProviderId,
+ newUserIdentifierInLookupTable);
+ if (convertedUser != null) {
+ /**
+ * @todo Display name is not being overwritten. Logic must be in Shib backing
+ * bean
+ */
+ AuthenticatedUser updatedInfoUser = authSvc.updateAuthenticatedUser(convertedUser, displayInfo);
+ if (updatedInfoUser != null) {
+ response.add("display name overwritten with", updatedInfoUser.getName());
+ } else {
+ problems.add("couldn't update display info");
+ }
+ } else {
+ problems.add("unable to convert user");
+ }
+ }
+ } else {
+ problems.add("couldn't find old username");
+ }
+ if (!knowsExistingPassword) {
+ String message = "User doesn't know password.";
+ problems.add(message);
+ /**
+ * @todo Someday we should make a errorResponse method that takes JSON arrays
+ * and objects.
+ */
+ return error(Status.BAD_REQUEST, problems.build().toString());
+ }
+ // response.add("knows existing password", knowsExistingPassword);
+ }
+
+ response.add("user to convert", builtInUserToConvert.getIdentifier());
+ response.add("existing user found by email (prompt to convert)", existing);
+ response.add("changing to this provider", shibProviderId);
+ response.add("value to overwrite old first name", overwriteFirstName);
+ response.add("value to overwrite old last name", overwriteLastName);
+ response.add("value to overwrite old email address", overwriteEmail);
+ if (overwriteAffiliation != null) {
+ response.add("affiliation", overwriteAffiliation);
+ }
+ response.add("problems", problems);
+ return ok(response);
+ }
+
+ /**
+ * This is used in testing via AdminIT.java but we don't expect sysadmins to use
+ * this.
+ */
+ @PUT
+ @AuthRequired
+ @Path("authenticatedUsers/convert/builtin2oauth")
+ public Response builtin2oauth(@Context ContainerRequestContext crc, String content) {
+ logger.info("entering builtin2oauth...");
+ try {
+ AuthenticatedUser userToRunThisMethod = getRequestAuthenticatedUserOrDie(crc);
+ if (!userToRunThisMethod.isSuperuser()) {
+ return error(Response.Status.FORBIDDEN, "Superusers only.");
+ }
+ } catch (WrappedResponse ex) {
+ return error(Response.Status.FORBIDDEN, "Superusers only.");
+ }
+ boolean disabled = false;
+ if (disabled) {
+ return error(Response.Status.BAD_REQUEST, "API endpoint disabled.");
+ }
+ AuthenticatedUser builtInUserToConvert = null;
+ String emailToFind;
+ String password;
+ String authuserId = "0"; // could let people specify id on authuser table. probably better to let them
+ // tell us their
+ String newEmailAddressToUse;
+ String newProviderId;
+ String newPersistentUserIdInLookupTable;
+ logger.info("content: " + content);
+ try {
+ String[] args = content.split(":");
+ emailToFind = args[0];
+ password = args[1];
+ newEmailAddressToUse = args[2];
+ newProviderId = args[3];
+ newPersistentUserIdInLookupTable = args[4];
+ // authuserId = args[666];
+ } catch (ArrayIndexOutOfBoundsException ex) {
+ return error(Response.Status.BAD_REQUEST, "Problem with content <<<" + content + ">>>: " + ex.toString());
+ }
+ AuthenticatedUser existingAuthUserFoundByEmail = shibService.findAuthUserByEmail(emailToFind);
+ String existing = "NOT FOUND";
+ if (existingAuthUserFoundByEmail != null) {
+ builtInUserToConvert = existingAuthUserFoundByEmail;
+ existing = existingAuthUserFoundByEmail.getIdentifier();
+ } else {
+ long longToLookup = Long.parseLong(authuserId);
+ AuthenticatedUser specifiedUserToConvert = authSvc.findByID(longToLookup);
+ if (specifiedUserToConvert != null) {
+ builtInUserToConvert = specifiedUserToConvert;
+ } else {
+ return error(Response.Status.BAD_REQUEST,
+ "No user to convert. We couldn't find a *single* existing user account based on " + emailToFind
+ + " and no user was found using specified id " + longToLookup);
+ }
+ }
+ // String shibProviderId = ShibAuthenticationProvider.PROVIDER_ID;
+ Map randomUser = authTestDataService.getRandomUser();
+ // String eppn = UUID.randomUUID().toString().substring(0, 8);
+ String eppn = randomUser.get("eppn");
+ String idPEntityId = randomUser.get("idp");
+ String notUsed = null;
+ String separator = "|";
+ // UserIdentifier newUserIdentifierInLookupTable = new
+ // UserIdentifier(idPEntityId + separator + eppn, notUsed);
+ UserIdentifier newUserIdentifierInLookupTable = new UserIdentifier(newPersistentUserIdInLookupTable, notUsed);
+ String overwriteFirstName = randomUser.get("firstName");
+ String overwriteLastName = randomUser.get("lastName");
+ String overwriteEmail = randomUser.get("email");
+ overwriteEmail = newEmailAddressToUse;
+ logger.info("overwriteEmail: " + overwriteEmail);
+ boolean validEmail = EMailValidator.isEmailValid(overwriteEmail);
+ if (!validEmail) {
+ // See https://github.com/IQSS/dataverse/issues/2998
+ return error(Response.Status.BAD_REQUEST, "invalid email: " + overwriteEmail);
+ }
+ /**
+ * @todo If affiliation is not null, put it in RoleAssigneeDisplayInfo
+ * constructor.
+ */
+ /**
+ * Here we are exercising (via an API test) shibService.getAffiliation with the
+ * TestShib IdP and a non-production DevShibAccountType.
+ */
+ // idPEntityId = ShibUtil.testShibIdpEntityId;
+ // String overwriteAffiliation = shibService.getAffiliation(idPEntityId,
+ // ShibServiceBean.DevShibAccountType.RANDOM);
+ String overwriteAffiliation = null;
+ logger.info("overwriteAffiliation: " + overwriteAffiliation);
+ /**
+ * @todo Find a place to put "position" in the authenticateduser table:
+ * https://github.com/IQSS/dataverse/issues/1444#issuecomment-74134694
+ */
+ String overwritePosition = "staff;student";
+ AuthenticatedUserDisplayInfo displayInfo = new AuthenticatedUserDisplayInfo(overwriteFirstName,
+ overwriteLastName, overwriteEmail, overwriteAffiliation, overwritePosition);
+ JsonObjectBuilder response = Json.createObjectBuilder();
+ JsonArrayBuilder problems = Json.createArrayBuilder();
+ if (password != null) {
+ response.add("password supplied", password);
+ boolean knowsExistingPassword = false;
+ BuiltinUser oldBuiltInUser = builtinUserService.findByUserName(builtInUserToConvert.getUserIdentifier());
+ if (oldBuiltInUser != null) {
+ String usernameOfBuiltinAccountToConvert = oldBuiltInUser.getUserName();
+ response.add("old username", usernameOfBuiltinAccountToConvert);
+ AuthenticatedUser authenticatedUser = authSvc.canLogInAsBuiltinUser(usernameOfBuiltinAccountToConvert,
+ password);
+ if (authenticatedUser != null) {
+ knowsExistingPassword = true;
+ AuthenticatedUser convertedUser = authSvc.convertBuiltInUserToRemoteUser(builtInUserToConvert,
+ newProviderId, newUserIdentifierInLookupTable);
+ if (convertedUser != null) {
+ /**
+ * @todo Display name is not being overwritten. Logic must be in Shib backing
+ * bean
+ */
+ AuthenticatedUser updatedInfoUser = authSvc.updateAuthenticatedUser(convertedUser, displayInfo);
+ if (updatedInfoUser != null) {
+ response.add("display name overwritten with", updatedInfoUser.getName());
+ } else {
+ problems.add("couldn't update display info");
+ }
+ } else {
+ problems.add("unable to convert user");
+ }
+ }
+ } else {
+ problems.add("couldn't find old username");
+ }
+ if (!knowsExistingPassword) {
+ String message = "User doesn't know password.";
+ problems.add(message);
+ /**
+ * @todo Someday we should make a errorResponse method that takes JSON arrays
+ * and objects.
+ */
+ return error(Status.BAD_REQUEST, problems.build().toString());
+ }
+ // response.add("knows existing password", knowsExistingPassword);
+ }
+
+ response.add("user to convert", builtInUserToConvert.getIdentifier());
+ response.add("existing user found by email (prompt to convert)", existing);
+ response.add("changing to this provider", newProviderId);
+ response.add("value to overwrite old first name", overwriteFirstName);
+ response.add("value to overwrite old last name", overwriteLastName);
+ response.add("value to overwrite old email address", overwriteEmail);
+ if (overwriteAffiliation != null) {
+ response.add("affiliation", overwriteAffiliation);
+ }
+ response.add("problems", problems);
+ return ok(response);
+ }
+
+
+
+
+ @Path("roles")
+ @POST
+ public Response createNewBuiltinRole(RoleDTO roleDto) {
+ ActionLogRecord alr = new ActionLogRecord(ActionLogRecord.ActionType.Admin, "createBuiltInRole")
+ .setInfo(roleDto.getAlias() + ":" + roleDto.getDescription());
+ try {
+ return ok(json(rolesSvc.save(roleDto.asRole())));
+ } catch (Exception e) {
+ alr.setActionResult(ActionLogRecord.Result.InternalError);
+ alr.setInfo(alr.getInfo() + "// " + e.getMessage());
+ return error(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage());
+ } finally {
+ actionLogSvc.log(alr);
+ }
+ }
+
+ @Path("roles")
+ @GET
+ public Response listBuiltinRoles() {
+ try {
+ return ok(rolesToJson(rolesSvc.findBuiltinRoles()));
+ } catch (Exception e) {
+ return error(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage());
+ }
+ }
@DELETE
- @AuthRequired
+ @AuthRequired
@Path("roles/{id}")
public Response deleteRole(@Context ContainerRequestContext crc, @PathParam("id") String id) {
@@ -1264,77 +1264,77 @@ public void write(OutputStream os) throws IOException,
return Response.ok(stream).build();
}
- @Path("assignments/assignees/{raIdtf: .*}")
- @GET
- public Response getAssignmentsFor(@PathParam("raIdtf") String raIdtf) {
-
- JsonArrayBuilder arr = Json.createArrayBuilder();
- roleAssigneeSvc.getAssignmentsFor(raIdtf).forEach(a -> arr.add(json(a)));
-
- return ok(arr);
- }
-
- /**
- * This method is used in integration tests.
- *
- * @param userId
- * The database id of an AuthenticatedUser.
- * @return The confirm email token.
- */
- @Path("confirmEmail/{userId}")
- @GET
- public Response getConfirmEmailToken(@PathParam("userId") long userId) {
- AuthenticatedUser user = authSvc.findByID(userId);
- if (user != null) {
- ConfirmEmailData confirmEmailData = confirmEmailSvc.findSingleConfirmEmailDataByUser(user);
- if (confirmEmailData != null) {
- return ok(Json.createObjectBuilder().add("token", confirmEmailData.getToken()));
- }
- }
- return error(Status.BAD_REQUEST, "Could not find confirm email token for user " + userId);
- }
-
- /**
- * This method is used in integration tests.
- *
- * @param userId
- * The database id of an AuthenticatedUser.
- */
- @Path("confirmEmail/{userId}")
- @POST
- public Response startConfirmEmailProcess(@PathParam("userId") long userId) {
- AuthenticatedUser user = authSvc.findByID(userId);
- if (user != null) {
- try {
- ConfirmEmailInitResponse confirmEmailInitResponse = confirmEmailSvc.beginConfirm(user);
- ConfirmEmailData confirmEmailData = confirmEmailInitResponse.getConfirmEmailData();
- return ok(Json.createObjectBuilder().add("tokenCreated", confirmEmailData.getCreated().toString())
- .add("identifier", user.getUserIdentifier()));
- } catch (ConfirmEmailException ex) {
- return error(Status.BAD_REQUEST,
- "Could not start confirm email process for user " + userId + ": " + ex.getLocalizedMessage());
- }
- }
- return error(Status.BAD_REQUEST, "Could not find user based on " + userId);
- }
-
- /**
- * This method is used by an integration test in UsersIT.java to exercise bug
- * https://github.com/IQSS/dataverse/issues/3287 . Not for use by users!
- */
- @Path("convertUserFromBcryptToSha1")
- @POST
- public Response convertUserFromBcryptToSha1(String json) {
- JsonReader jsonReader = Json.createReader(new StringReader(json));
- JsonObject object = jsonReader.readObject();
- jsonReader.close();
- BuiltinUser builtinUser = builtinUserService.find(new Long(object.getInt("builtinUserId")));
- builtinUser.updateEncryptedPassword("4G7xxL9z11/JKN4jHPn4g9iIQck=", 0); // password is "sha-1Pass", 0 means
- // SHA-1
- BuiltinUser savedUser = builtinUserService.save(builtinUser);
- return ok("foo: " + savedUser);
-
- }
+ @Path("assignments/assignees/{raIdtf: .*}")
+ @GET
+ public Response getAssignmentsFor(@PathParam("raIdtf") String raIdtf) {
+
+ JsonArrayBuilder arr = Json.createArrayBuilder();
+ roleAssigneeSvc.getAssignmentsFor(raIdtf).forEach(a -> arr.add(json(a)));
+
+ return ok(arr);
+ }
+
+ /**
+ * This method is used in integration tests.
+ *
+ * @param userId
+ * The database id of an AuthenticatedUser.
+ * @return The confirm email token.
+ */
+ @Path("confirmEmail/{userId}")
+ @GET
+ public Response getConfirmEmailToken(@PathParam("userId") long userId) {
+ AuthenticatedUser user = authSvc.findByID(userId);
+ if (user != null) {
+ ConfirmEmailData confirmEmailData = confirmEmailSvc.findSingleConfirmEmailDataByUser(user);
+ if (confirmEmailData != null) {
+ return ok(Json.createObjectBuilder().add("token", confirmEmailData.getToken()));
+ }
+ }
+ return error(Status.BAD_REQUEST, "Could not find confirm email token for user " + userId);
+ }
+
+ /**
+ * This method is used in integration tests.
+ *
+ * @param userId
+ * The database id of an AuthenticatedUser.
+ */
+ @Path("confirmEmail/{userId}")
+ @POST
+ public Response startConfirmEmailProcess(@PathParam("userId") long userId) {
+ AuthenticatedUser user = authSvc.findByID(userId);
+ if (user != null) {
+ try {
+ ConfirmEmailInitResponse confirmEmailInitResponse = confirmEmailSvc.beginConfirm(user);
+ ConfirmEmailData confirmEmailData = confirmEmailInitResponse.getConfirmEmailData();
+ return ok(Json.createObjectBuilder().add("tokenCreated", confirmEmailData.getCreated().toString())
+ .add("identifier", user.getUserIdentifier()));
+ } catch (ConfirmEmailException ex) {
+ return error(Status.BAD_REQUEST,
+ "Could not start confirm email process for user " + userId + ": " + ex.getLocalizedMessage());
+ }
+ }
+ return error(Status.BAD_REQUEST, "Could not find user based on " + userId);
+ }
+
+ /**
+ * This method is used by an integration test in UsersIT.java to exercise bug
+ * https://github.com/IQSS/dataverse/issues/3287 . Not for use by users!
+ */
+ @Path("convertUserFromBcryptToSha1")
+ @POST
+ public Response convertUserFromBcryptToSha1(String json) {
+ JsonReader jsonReader = Json.createReader(new StringReader(json));
+ JsonObject object = jsonReader.readObject();
+ jsonReader.close();
+ BuiltinUser builtinUser = builtinUserService.find(new Long(object.getInt("builtinUserId")));
+ builtinUser.updateEncryptedPassword("4G7xxL9z11/JKN4jHPn4g9iIQck=", 0); // password is "sha-1Pass", 0 means
+ // SHA-1
+ BuiltinUser savedUser = builtinUserService.save(builtinUser);
+ return ok("foo: " + savedUser);
+
+ }
@Path("permissions/{dvo}")
@AuthRequired
@@ -1355,43 +1355,43 @@ public Response findPermissonsOn(@Context final ContainerRequestContext crc, @Pa
}
}
- @Path("assignee/{idtf}")
- @GET
- public Response findRoleAssignee(@PathParam("idtf") String idtf) {
- RoleAssignee ra = roleAssigneeSvc.getRoleAssignee(idtf);
- return (ra == null) ? notFound("Role Assignee '" + idtf + "' not found.") : ok(json(ra.getDisplayInfo()));
- }
-
- @Path("datasets/integrity/{datasetVersionId}/fixmissingunf")
- @POST
- public Response fixUnf(@PathParam("datasetVersionId") String datasetVersionId,
- @QueryParam("forceRecalculate") boolean forceRecalculate) {
- JsonObjectBuilder info = datasetVersionSvc.fixMissingUnf(datasetVersionId, forceRecalculate);
- return ok(info);
- }
-
- @Path("datafiles/integrity/fixmissingoriginaltypes")
- @GET
- public Response fixMissingOriginalTypes() {
- JsonObjectBuilder info = Json.createObjectBuilder();
-
- List affectedFileIds = fileService.selectFilesWithMissingOriginalTypes();
-
- if (affectedFileIds.isEmpty()) {
- info.add("message",
- "All the tabular files in the database already have the original types set correctly; exiting.");
- } else {
- for (Long fileid : affectedFileIds) {
- logger.fine("found file id: " + fileid);
- }
- info.add("message", "Found " + affectedFileIds.size()
- + " tabular files with missing original types. Kicking off an async job that will repair the files in the background.");
- }
-
- ingestService.fixMissingOriginalTypes(affectedFileIds);
-
- return ok(info);
- }
+ @Path("assignee/{idtf}")
+ @GET
+ public Response findRoleAssignee(@PathParam("idtf") String idtf) {
+ RoleAssignee ra = roleAssigneeSvc.getRoleAssignee(idtf);
+ return (ra == null) ? notFound("Role Assignee '" + idtf + "' not found.") : ok(json(ra.getDisplayInfo()));
+ }
+
+ @Path("datasets/integrity/{datasetVersionId}/fixmissingunf")
+ @POST
+ public Response fixUnf(@PathParam("datasetVersionId") String datasetVersionId,
+ @QueryParam("forceRecalculate") boolean forceRecalculate) {
+ JsonObjectBuilder info = datasetVersionSvc.fixMissingUnf(datasetVersionId, forceRecalculate);
+ return ok(info);
+ }
+
+ @Path("datafiles/integrity/fixmissingoriginaltypes")
+ @GET
+ public Response fixMissingOriginalTypes() {
+ JsonObjectBuilder info = Json.createObjectBuilder();
+
+ List affectedFileIds = fileService.selectFilesWithMissingOriginalTypes();
+
+ if (affectedFileIds.isEmpty()) {
+ info.add("message",
+ "All the tabular files in the database already have the original types set correctly; exiting.");
+ } else {
+ for (Long fileid : affectedFileIds) {
+ logger.fine("found file id: " + fileid);
+ }
+ info.add("message", "Found " + affectedFileIds.size()
+ + " tabular files with missing original types. Kicking off an async job that will repair the files in the background.");
+ }
+
+ ingestService.fixMissingOriginalTypes(affectedFileIds);
+
+ return ok(info);
+ }
@Path("datafiles/integrity/fixmissingoriginalsizes")
@GET
@@ -1421,60 +1421,60 @@ public Response fixMissingOriginalSizes(@QueryParam("limit") Integer limit) {
return ok(info);
}
- /**
- * This method is used in API tests, called from UtilIt.java.
- */
- @GET
- @Path("datasets/thumbnailMetadata/{id}")
- public Response getDatasetThumbnailMetadata(@PathParam("id") Long idSupplied) {
- Dataset dataset = datasetSvc.find(idSupplied);
- if (dataset == null) {
- return error(Response.Status.NOT_FOUND, "Could not find dataset based on id supplied: " + idSupplied + ".");
- }
- JsonObjectBuilder data = Json.createObjectBuilder();
- DatasetThumbnail datasetThumbnail = dataset.getDatasetThumbnail(ImageThumbConverter.DEFAULT_CARDIMAGE_SIZE);
- data.add("isUseGenericThumbnail", dataset.isUseGenericThumbnail());
- data.add("datasetLogoPresent", DatasetUtil.isDatasetLogoPresent(dataset, ImageThumbConverter.DEFAULT_CARDIMAGE_SIZE));
- if (datasetThumbnail != null) {
- data.add("datasetThumbnailBase64image", datasetThumbnail.getBase64image());
- DataFile dataFile = datasetThumbnail.getDataFile();
- if (dataFile != null) {
- /**
- * @todo Change this from a String to a long.
- */
- data.add("dataFileId", dataFile.getId().toString());
- }
- }
- return ok(data);
- }
-
- /**
- * validatePassword
- *
- * Validate a password with an API call
- *
- * @param password
- * The password
- * @return A response with the validation result.
- */
- @Path("validatePassword")
- @POST
- public Response validatePassword(String password) {
-
- final List errors = passwordValidatorService.validate(password, new Date(), false);
- final JsonArrayBuilder errorArray = Json.createArrayBuilder();
- errors.forEach(errorArray::add);
- return ok(Json.createObjectBuilder().add("password", password).add("errors", errorArray));
- }
-
- @GET
- @Path("/isOrcid")
- public Response isOrcidEnabled() {
- return authSvc.isOrcidEnabled() ? ok("Orcid is enabled") : ok("no orcid for you.");
- }
+ /**
+ * This method is used in API tests, called from UtilIt.java.
+ */
+ @GET
+ @Path("datasets/thumbnailMetadata/{id}")
+ public Response getDatasetThumbnailMetadata(@PathParam("id") Long idSupplied) {
+ Dataset dataset = datasetSvc.find(idSupplied);
+ if (dataset == null) {
+ return error(Response.Status.NOT_FOUND, "Could not find dataset based on id supplied: " + idSupplied + ".");
+ }
+ JsonObjectBuilder data = Json.createObjectBuilder();
+ DatasetThumbnail datasetThumbnail = dataset.getDatasetThumbnail(ImageThumbConverter.DEFAULT_CARDIMAGE_SIZE);
+ data.add("isUseGenericThumbnail", dataset.isUseGenericThumbnail());
+ data.add("datasetLogoPresent", DatasetUtil.isDatasetLogoPresent(dataset, ImageThumbConverter.DEFAULT_CARDIMAGE_SIZE));
+ if (datasetThumbnail != null) {
+ data.add("datasetThumbnailBase64image", datasetThumbnail.getBase64image());
+ DataFile dataFile = datasetThumbnail.getDataFile();
+ if (dataFile != null) {
+ /**
+ * @todo Change this from a String to a long.
+ */
+ data.add("dataFileId", dataFile.getId().toString());
+ }
+ }
+ return ok(data);
+ }
+
+ /**
+ * validatePassword
+ *
+ * Validate a password with an API call
+ *
+ * @param password
+ * The password
+ * @return A response with the validation result.
+ */
+ @Path("validatePassword")
+ @POST
+ public Response validatePassword(String password) {
+
+ final List errors = passwordValidatorService.validate(password, new Date(), false);
+ final JsonArrayBuilder errorArray = Json.createArrayBuilder();
+ errors.forEach(errorArray::add);
+ return ok(Json.createObjectBuilder().add("password", password).add("errors", errorArray));
+ }
+
+ @GET
+ @Path("/isOrcid")
+ public Response isOrcidEnabled() {
+ return authSvc.isOrcidEnabled() ? ok("Orcid is enabled") : ok("no orcid for you.");
+ }
@POST
- @AuthRequired
+ @AuthRequired
@Path("{id}/reregisterHDLToPID")
public Response reregisterHdlToPID(@Context ContainerRequestContext crc, @PathParam("id") String id) {
logger.info("Starting to reregister " + id + " Dataset Id. (from hdl to doi)" + new Date());
@@ -1805,7 +1805,7 @@ public Response updateHashValues(@Context ContainerRequestContext crc, @PathPara
}
@POST
- @AuthRequired
+ @AuthRequired
@Path("/computeDataFileHashValue/{fileId}/algorithm/{alg}")
public Response computeDataFileHashValue(@Context ContainerRequestContext crc, @PathParam("fileId") String fileId, @PathParam("alg") String alg) {
@@ -1867,7 +1867,7 @@ public Response computeDataFileHashValue(@Context ContainerRequestContext crc, @
}
@POST
- @AuthRequired
+ @AuthRequired
@Path("/validateDataFileHashValue/{fileId}")
public Response validateDataFileHashValue(@Context ContainerRequestContext crc, @PathParam("fileId") String fileId) {
@@ -1934,7 +1934,7 @@ public Response validateDataFileHashValue(@Context ContainerRequestContext crc,
}
@POST
- @AuthRequired
+ @AuthRequired
@Path("/submitDatasetVersionToArchive/{id}/{version}")
public Response submitDatasetVersionToArchive(@Context ContainerRequestContext crc, @PathParam("id") String dsid,
@PathParam("version") String versionNumber) {
@@ -2007,7 +2007,7 @@ public void run() {
* @return
*/
@POST
- @AuthRequired
+ @AuthRequired
@Path("/archiveAllUnarchivedDatasetVersions")
public Response archiveAllUnarchivedDatasetVersions(@Context ContainerRequestContext crc, @QueryParam("listonly") boolean listonly, @QueryParam("limit") Integer limit, @QueryParam("latestonly") boolean latestonly) {
@@ -2106,7 +2106,7 @@ public Response clearMetricsCacheByName(@PathParam("name") String name) {
}
@GET
- @AuthRequired
+ @AuthRequired
@Path("/dataverse/{alias}/addRoleAssignmentsToChildren")
public Response addRoleAssignementsToChildren(@Context ContainerRequestContext crc, @PathParam("alias") String alias) throws WrappedResponse {
Dataverse owner = dataverseSvc.findByAlias(alias);
@@ -2137,90 +2137,90 @@ public Response addRoleAssignementsToChildren(@Context ContainerRequestContext c
}
@GET
- @AuthRequired
+ @AuthRequired
@Path("/dataverse/{alias}/storageDriver")
public Response getStorageDriver(@Context ContainerRequestContext crc, @PathParam("alias") String alias) throws WrappedResponse {
- Dataverse dataverse = dataverseSvc.findByAlias(alias);
- if (dataverse == null) {
- return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + ".");
- }
- try {
- AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
- if (!user.isSuperuser()) {
- return error(Response.Status.FORBIDDEN, "Superusers only.");
- }
- } catch (WrappedResponse wr) {
- return wr.getResponse();
- }
- //Note that this returns what's set directly on this dataverse. If null/DataAccess.UNDEFINED_STORAGE_DRIVER_IDENTIFIER, the user would have to recurse the chain of parents to find the effective storageDriver
- return ok(dataverse.getStorageDriverId());
+ Dataverse dataverse = dataverseSvc.findByAlias(alias);
+ if (dataverse == null) {
+ return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + ".");
+ }
+ try {
+ AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
+ if (!user.isSuperuser()) {
+ return error(Response.Status.FORBIDDEN, "Superusers only.");
+ }
+ } catch (WrappedResponse wr) {
+ return wr.getResponse();
+ }
+ //Note that this returns what's set directly on this dataverse. If null/DataAccess.UNDEFINED_STORAGE_DRIVER_IDENTIFIER, the user would have to recurse the chain of parents to find the effective storageDriver
+ return ok(dataverse.getStorageDriverId());
}
@PUT
- @AuthRequired
+ @AuthRequired
@Path("/dataverse/{alias}/storageDriver")
public Response setStorageDriver(@Context ContainerRequestContext crc, @PathParam("alias") String alias, String label) throws WrappedResponse {
- Dataverse dataverse = dataverseSvc.findByAlias(alias);
- if (dataverse == null) {
- return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + ".");
- }
- try {
- AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
- if (!user.isSuperuser()) {
- return error(Response.Status.FORBIDDEN, "Superusers only.");
- }
- } catch (WrappedResponse wr) {
- return wr.getResponse();
- }
- for (Entry store: DataAccess.getStorageDriverLabels().entrySet()) {
- if(store.getKey().equals(label)) {
- dataverse.setStorageDriverId(store.getValue());
- return ok("Storage set to: " + store.getKey() + "/" + store.getValue());
- }
- }
- return error(Response.Status.BAD_REQUEST,
- "No Storage Driver found for : " + label);
+ Dataverse dataverse = dataverseSvc.findByAlias(alias);
+ if (dataverse == null) {
+ return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + ".");
+ }
+ try {
+ AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
+ if (!user.isSuperuser()) {
+ return error(Response.Status.FORBIDDEN, "Superusers only.");
+ }
+ } catch (WrappedResponse wr) {
+ return wr.getResponse();
+ }
+ for (Entry store: DataAccess.getStorageDriverLabels().entrySet()) {
+ if(store.getKey().equals(label)) {
+ dataverse.setStorageDriverId(store.getValue());
+ return ok("Storage set to: " + store.getKey() + "/" + store.getValue());
+ }
+ }
+ return error(Response.Status.BAD_REQUEST,
+ "No Storage Driver found for : " + label);
}
@DELETE
- @AuthRequired
+ @AuthRequired
@Path("/dataverse/{alias}/storageDriver")
public Response resetStorageDriver(@Context ContainerRequestContext crc, @PathParam("alias") String alias) throws WrappedResponse {
- Dataverse dataverse = dataverseSvc.findByAlias(alias);
- if (dataverse == null) {
- return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + ".");
- }
- try {
- AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
- if (!user.isSuperuser()) {
- return error(Response.Status.FORBIDDEN, "Superusers only.");
- }
- } catch (WrappedResponse wr) {
- return wr.getResponse();
- }
- dataverse.setStorageDriverId("");
- return ok("Storage reset to default: " + DataAccess.DEFAULT_STORAGE_DRIVER_IDENTIFIER);
+ Dataverse dataverse = dataverseSvc.findByAlias(alias);
+ if (dataverse == null) {
+ return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + ".");
+ }
+ try {
+ AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
+ if (!user.isSuperuser()) {
+ return error(Response.Status.FORBIDDEN, "Superusers only.");
+ }
+ } catch (WrappedResponse wr) {
+ return wr.getResponse();
+ }
+ dataverse.setStorageDriverId("");
+ return ok("Storage reset to default: " + DataAccess.DEFAULT_STORAGE_DRIVER_IDENTIFIER);
}
@GET
- @AuthRequired
+ @AuthRequired
@Path("/dataverse/storageDrivers")
public Response listStorageDrivers(@Context ContainerRequestContext crc) throws WrappedResponse {
- try {
- AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
- if (!user.isSuperuser()) {
- return error(Response.Status.FORBIDDEN, "Superusers only.");
- }
- } catch (WrappedResponse wr) {
- return wr.getResponse();
- }
- JsonObjectBuilder bld = jsonObjectBuilder();
- DataAccess.getStorageDriverLabels().entrySet().forEach(s -> bld.add(s.getKey(), s.getValue()));
- return ok(bld);
+ try {
+ AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
+ if (!user.isSuperuser()) {
+ return error(Response.Status.FORBIDDEN, "Superusers only.");
+ }
+ } catch (WrappedResponse wr) {
+ return wr.getResponse();
+ }
+ JsonObjectBuilder bld = jsonObjectBuilder();
+ DataAccess.getStorageDriverLabels().entrySet().forEach(s -> bld.add(s.getKey(), s.getValue()));
+ return ok(bld);
}
@GET
- @AuthRequired
+ @AuthRequired
@Path("/dataverse/{alias}/curationLabelSet")
public Response getCurationLabelSet(@Context ContainerRequestContext crc, @PathParam("alias") String alias) throws WrappedResponse {
Dataverse dataverse = dataverseSvc.findByAlias(alias);
@@ -2242,7 +2242,7 @@ public Response getCurationLabelSet(@Context ContainerRequestContext crc, @PathP
}
@PUT
- @AuthRequired
+ @AuthRequired
@Path("/dataverse/{alias}/curationLabelSet")
public Response setCurationLabelSet(@Context ContainerRequestContext crc, @PathParam("alias") String alias, @QueryParam("name") String name) throws WrappedResponse {
Dataverse dataverse = dataverseSvc.findByAlias(alias);
@@ -2273,7 +2273,7 @@ public Response setCurationLabelSet(@Context ContainerRequestContext crc, @PathP
}
@DELETE
- @AuthRequired
+ @AuthRequired
@Path("/dataverse/{alias}/curationLabelSet")
public Response resetCurationLabelSet(@Context ContainerRequestContext crc, @PathParam("alias") String alias) throws WrappedResponse {
Dataverse dataverse = dataverseSvc.findByAlias(alias);
@@ -2293,7 +2293,7 @@ public Response resetCurationLabelSet(@Context ContainerRequestContext crc, @Pat
}
@GET
- @AuthRequired
+ @AuthRequired
@Path("/dataverse/curationLabelSets")
public Response listCurationLabelSets(@Context ContainerRequestContext crc) throws WrappedResponse {
try {
@@ -2403,7 +2403,7 @@ public Response getBannerMessages(@PathParam("id") Long id) throws WrappedRespon
}
@POST
- @AuthRequired
+ @AuthRequired
@Consumes("application/json")
@Path("/requestSignedUrl")
public Response getSignedUrl(@Context ContainerRequestContext crc, JsonObject urlInfo) {
@@ -2521,162 +2521,162 @@ public Response getFeatureFlag(@PathParam("flag") String flagIn) {
}
}
- @GET
- @AuthRequired
- @Path("/datafiles/auditFiles")
- public Response getAuditFiles(@Context ContainerRequestContext crc,
- @QueryParam("firstId") Long firstId, @QueryParam("lastId") Long lastId,
- @QueryParam("datasetIdentifierList") String datasetIdentifierList) throws WrappedResponse {
- try {
- AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
- if (!user.isSuperuser()) {
- return error(Response.Status.FORBIDDEN, "Superusers only.");
- }
- } catch (WrappedResponse wr) {
- return wr.getResponse();
- }
-
- int datasetsChecked = 0;
- long startId = (firstId == null ? 0 : firstId);
- long endId = (lastId == null ? Long.MAX_VALUE : lastId);
-
- List datasetIdentifiers;
- if (datasetIdentifierList == null || datasetIdentifierList.isEmpty()) {
- datasetIdentifiers = Collections.emptyList();
- } else {
- startId = 0;
- endId = Long.MAX_VALUE;
- datasetIdentifiers = List.of(datasetIdentifierList.split(","));
- }
- if (endId < startId) {
- return badRequest("Invalid Parameters: lastId must be equal to or greater than firstId");
- }
-
- NullSafeJsonBuilder jsonObjectBuilder = NullSafeJsonBuilder.jsonObjectBuilder();
- JsonArrayBuilder jsonDatasetsArrayBuilder = Json.createArrayBuilder();
- JsonArrayBuilder jsonFailuresArrayBuilder = Json.createArrayBuilder();
-
- if (startId > 0) {
- jsonObjectBuilder.add("firstId", startId);
- }
- if (endId < Long.MAX_VALUE) {
- jsonObjectBuilder.add("lastId", endId);
- }
-
- // compile the list of ids to process
- List datasetIds;
- if (datasetIdentifiers.isEmpty()) {
- datasetIds = datasetService.findAllLocalDatasetIds();
- } else {
- datasetIds = new ArrayList<>(datasetIdentifiers.size());
- JsonArrayBuilder jab = Json.createArrayBuilder();
- datasetIdentifiers.forEach(id -> {
- String dId = id.trim();
- jab.add(dId);
- Dataset d = datasetService.findByGlobalId(dId);
- if (d != null) {
- datasetIds.add(d.getId());
- } else {
- NullSafeJsonBuilder job = NullSafeJsonBuilder.jsonObjectBuilder();
- job.add("datasetIdentifier",dId);
- job.add("reason","Not Found");
- jsonFailuresArrayBuilder.add(job);
- }
- });
- jsonObjectBuilder.add("datasetIdentifierList", jab);
- }
-
- for (Long datasetId : datasetIds) {
- if (datasetId < startId) {
- continue;
- } else if (datasetId > endId) {
- break;
- }
- Dataset dataset;
- try {
- dataset = findDatasetOrDie(String.valueOf(datasetId));
- datasetsChecked++;
- } catch (WrappedResponse e) {
- NullSafeJsonBuilder job = NullSafeJsonBuilder.jsonObjectBuilder();
- job.add("datasetId", datasetId);
- job.add("reason", e.getMessage());
- jsonFailuresArrayBuilder.add(job);
- continue;
- }
-
- List missingFiles = new ArrayList<>();
- List missingFileMetadata = new ArrayList<>();
- try {
- Predicate filter = s -> true;
- StorageIO datasetIO = DataAccess.getStorageIO(dataset);
- final List result = datasetIO.cleanUp(filter, true);
- // add files that are in dataset files but not in cleanup result or DataFiles with missing FileMetadata
- dataset.getFiles().forEach(df -> {
- try {
- StorageIO datafileIO = df.getStorageIO();
- String storageId = df.getStorageIdentifier();
- FileMetadata fm = df.getFileMetadata();
- if (!datafileIO.exists()) {
- missingFiles.add(storageId + "," + (fm != null ?
- (fm.getDirectoryLabel() != null || !fm.getDirectoryLabel().isEmpty() ? "directoryLabel,"+fm.getDirectoryLabel()+"," : "")
- +"label,"+fm.getLabel() : "type,"+df.getContentType()));
- }
- if (fm == null) {
- missingFileMetadata.add(storageId + ",dataFileId," + df.getId());
- }
- } catch (IOException e) {
- NullSafeJsonBuilder job = NullSafeJsonBuilder.jsonObjectBuilder();
- job.add("dataFileId", df.getId());
- job.add("reason", e.getMessage());
- jsonFailuresArrayBuilder.add(job);
- }
- });
- } catch (IOException e) {
- NullSafeJsonBuilder job = NullSafeJsonBuilder.jsonObjectBuilder();
- job.add("datasetId", datasetId);
- job.add("reason", e.getMessage());
- jsonFailuresArrayBuilder.add(job);
- }
-
- JsonObjectBuilder job = Json.createObjectBuilder();
- if (!missingFiles.isEmpty() || !missingFileMetadata.isEmpty()) {
- job.add("id", dataset.getId());
- job.add("identifier", dataset.getIdentifier());
- job.add("authority", dataset.getAuthority());
- job.add("protocol", dataset.getProtocol());
- job.add("persistentURL", dataset.getPersistentURL());
- if (!missingFileMetadata.isEmpty()) {
- JsonArrayBuilder jabMissingFileMetadata = Json.createArrayBuilder();
- missingFileMetadata.forEach(mm -> {
- String[] missingMetadata = mm.split(",");
- NullSafeJsonBuilder jobj = NullSafeJsonBuilder.jsonObjectBuilder()
- .add("storageIdentifier", missingMetadata[0])
- .add(missingMetadata[1], missingMetadata[2]);
- jabMissingFileMetadata.add(jobj);
- });
- job.add("missingFileMetadata", jabMissingFileMetadata);
- }
- if (!missingFiles.isEmpty()) {
- JsonArrayBuilder jabMissingFiles = Json.createArrayBuilder();
- missingFiles.forEach(mf -> {
- String[] missingFile = mf.split(",");
- NullSafeJsonBuilder jobj = NullSafeJsonBuilder.jsonObjectBuilder()
- .add("storageIdentifier", missingFile[0]);
- for (int i = 2; i < missingFile.length; i+=2) {
- jobj.add(missingFile[i-1], missingFile[i]);
- }
- jabMissingFiles.add(jobj);
- });
- job.add("missingFiles", jabMissingFiles);
- }
- jsonDatasetsArrayBuilder.add(job);
- }
- }
-
- jsonObjectBuilder.add("datasetsChecked", datasetsChecked);
- jsonObjectBuilder.add("datasets", jsonDatasetsArrayBuilder);
- jsonObjectBuilder.add("failures", jsonFailuresArrayBuilder);
-
- return ok(jsonObjectBuilder);
- }
+ @GET
+ @AuthRequired
+ @Path("/datafiles/auditFiles")
+ public Response getAuditFiles(@Context ContainerRequestContext crc,
+ @QueryParam("firstId") Long firstId, @QueryParam("lastId") Long lastId,
+ @QueryParam("datasetIdentifierList") String datasetIdentifierList) throws WrappedResponse {
+ try {
+ AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
+ if (!user.isSuperuser()) {
+ return error(Response.Status.FORBIDDEN, "Superusers only.");
+ }
+ } catch (WrappedResponse wr) {
+ return wr.getResponse();
+ }
+
+ int datasetsChecked = 0;
+ long startId = (firstId == null ? 0 : firstId);
+ long endId = (lastId == null ? Long.MAX_VALUE : lastId);
+
+ List datasetIdentifiers;
+ if (datasetIdentifierList == null || datasetIdentifierList.isEmpty()) {
+ datasetIdentifiers = Collections.emptyList();
+ } else {
+ startId = 0;
+ endId = Long.MAX_VALUE;
+ datasetIdentifiers = List.of(datasetIdentifierList.split(","));
+ }
+ if (endId < startId) {
+ return badRequest("Invalid Parameters: lastId must be equal to or greater than firstId");
+ }
+
+ NullSafeJsonBuilder jsonObjectBuilder = NullSafeJsonBuilder.jsonObjectBuilder();
+ JsonArrayBuilder jsonDatasetsArrayBuilder = Json.createArrayBuilder();
+ JsonArrayBuilder jsonFailuresArrayBuilder = Json.createArrayBuilder();
+
+ if (startId > 0) {
+ jsonObjectBuilder.add("firstId", startId);
+ }
+ if (endId < Long.MAX_VALUE) {
+ jsonObjectBuilder.add("lastId", endId);
+ }
+
+ // compile the list of ids to process
+ List datasetIds;
+ if (datasetIdentifiers.isEmpty()) {
+ datasetIds = datasetService.findAllLocalDatasetIds();
+ } else {
+ datasetIds = new ArrayList<>(datasetIdentifiers.size());
+ JsonArrayBuilder jab = Json.createArrayBuilder();
+ datasetIdentifiers.forEach(id -> {
+ String dId = id.trim();
+ jab.add(dId);
+ Dataset d = datasetService.findByGlobalId(dId);
+ if (d != null) {
+ datasetIds.add(d.getId());
+ } else {
+ NullSafeJsonBuilder job = NullSafeJsonBuilder.jsonObjectBuilder();
+ job.add("datasetIdentifier",dId);
+ job.add("reason","Not Found");
+ jsonFailuresArrayBuilder.add(job);
+ }
+ });
+ jsonObjectBuilder.add("datasetIdentifierList", jab);
+ }
+
+ for (Long datasetId : datasetIds) {
+ if (datasetId < startId) {
+ continue;
+ } else if (datasetId > endId) {
+ break;
+ }
+ Dataset dataset;
+ try {
+ dataset = findDatasetOrDie(String.valueOf(datasetId));
+ datasetsChecked++;
+ } catch (WrappedResponse e) {
+ NullSafeJsonBuilder job = NullSafeJsonBuilder.jsonObjectBuilder();
+ job.add("datasetId", datasetId);
+ job.add("reason", e.getMessage());
+ jsonFailuresArrayBuilder.add(job);
+ continue;
+ }
+
+ List missingFiles = new ArrayList<>();
+ List missingFileMetadata = new ArrayList<>();
+ try {
+ Predicate filter = s -> true;
+ StorageIO datasetIO = DataAccess.getStorageIO(dataset);
+ final List result = datasetIO.cleanUp(filter, true);
+ // add files that are in dataset files but not in cleanup result or DataFiles with missing FileMetadata
+ dataset.getFiles().forEach(df -> {
+ try {
+ StorageIO datafileIO = df.getStorageIO();
+ String storageId = df.getStorageIdentifier();
+ FileMetadata fm = df.getFileMetadata();
+ if (!datafileIO.exists()) {
+ missingFiles.add(storageId + "," + (fm != null ?
+ (fm.getDirectoryLabel() != null || !fm.getDirectoryLabel().isEmpty() ? "directoryLabel,"+fm.getDirectoryLabel()+"," : "")
+ +"label,"+fm.getLabel() : "type,"+df.getContentType()));
+ }
+ if (fm == null) {
+ missingFileMetadata.add(storageId + ",dataFileId," + df.getId());
+ }
+ } catch (IOException e) {
+ NullSafeJsonBuilder job = NullSafeJsonBuilder.jsonObjectBuilder();
+ job.add("dataFileId", df.getId());
+ job.add("reason", e.getMessage());
+ jsonFailuresArrayBuilder.add(job);
+ }
+ });
+ } catch (IOException e) {
+ NullSafeJsonBuilder job = NullSafeJsonBuilder.jsonObjectBuilder();
+ job.add("datasetId", datasetId);
+ job.add("reason", e.getMessage());
+ jsonFailuresArrayBuilder.add(job);
+ }
+
+ JsonObjectBuilder job = Json.createObjectBuilder();
+ if (!missingFiles.isEmpty() || !missingFileMetadata.isEmpty()) {
+ job.add("id", dataset.getId());
+ job.add("identifier", dataset.getIdentifier());
+ job.add("authority", dataset.getAuthority());
+ job.add("protocol", dataset.getProtocol());
+ job.add("persistentURL", dataset.getPersistentURL());
+ if (!missingFileMetadata.isEmpty()) {
+ JsonArrayBuilder jabMissingFileMetadata = Json.createArrayBuilder();
+ missingFileMetadata.forEach(mm -> {
+ String[] missingMetadata = mm.split(",");
+ NullSafeJsonBuilder jobj = NullSafeJsonBuilder.jsonObjectBuilder()
+ .add("storageIdentifier", missingMetadata[0])
+ .add(missingMetadata[1], missingMetadata[2]);
+ jabMissingFileMetadata.add(jobj);
+ });
+ job.add("missingFileMetadata", jabMissingFileMetadata);
+ }
+ if (!missingFiles.isEmpty()) {
+ JsonArrayBuilder jabMissingFiles = Json.createArrayBuilder();
+ missingFiles.forEach(mf -> {
+ String[] missingFile = mf.split(",");
+ NullSafeJsonBuilder jobj = NullSafeJsonBuilder.jsonObjectBuilder()
+ .add("storageIdentifier", missingFile[0]);
+ for (int i = 2; i < missingFile.length; i+=2) {
+ jobj.add(missingFile[i-1], missingFile[i]);
+ }
+ jabMissingFiles.add(jobj);
+ });
+ job.add("missingFiles", jabMissingFiles);
+ }
+ jsonDatasetsArrayBuilder.add(job);
+ }
+ }
+
+ jsonObjectBuilder.add("datasetsChecked", datasetsChecked);
+ jsonObjectBuilder.add("datasets", jsonDatasetsArrayBuilder);
+ jsonObjectBuilder.add("failures", jsonFailuresArrayBuilder);
+
+ return ok(jsonObjectBuilder);
+ }
}
From a79015f6785fbd572e01033584dfafe25fb153bc Mon Sep 17 00:00:00 2001
From: Don Sizemore
Date: Wed, 20 Nov 2024 10:09:41 -0500
Subject: [PATCH 178/270] #11037 limit beta testing deployments to one
concurrent action at a time
---
.github/workflows/deploy_beta_testing.yml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/.github/workflows/deploy_beta_testing.yml b/.github/workflows/deploy_beta_testing.yml
index 4cec08564a4..eca8416732a 100644
--- a/.github/workflows/deploy_beta_testing.yml
+++ b/.github/workflows/deploy_beta_testing.yml
@@ -5,6 +5,10 @@ on:
branches:
- develop
+concurrency:
+ group: deploy-beta-testing
+ cancel-in-progress: false
+
jobs:
build:
runs-on: ubuntu-latest
From 2db26b20f3d01895cfa4d0c5093ca1ce4b539be2 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Wed, 20 Nov 2024 10:26:56 -0500
Subject: [PATCH 179/270] add pid
---
doc/sphinx-guides/source/api/native-api.rst | 16 ++++++----------
.../java/edu/harvard/iq/dataverse/api/Admin.java | 4 +---
2 files changed, 7 insertions(+), 13 deletions(-)
diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst
index 84e8bf45d9d..9a5f469a4d0 100644
--- a/doc/sphinx-guides/source/api/native-api.rst
+++ b/doc/sphinx-guides/source/api/native-api.rst
@@ -6226,17 +6226,15 @@ Sample JSON Audit Response::
"firstId": 0,
"lastId": 100,
"datasetIdentifierList": [
- "doi.org/10.5072/FK2/XXXXXX",
- "doi.org/10.5072/FK2/JXYBJS",
- "doi.org/10.7910/DVN/MPU019"
+ "doi:10.5072/FK2/XXXXXX",
+ "doi:10.5072/FK2/JXYBJS",
+ "doi:10.7910/DVN/MPU019"
],
"datasetsChecked": 100,
"datasets": [
{
"id": 6,
- "identifier": "FK2/JXYBJS",
- "authority": "10.5072",
- "protocol": "doi",
+ "pid": "doi:10.5072/FK2/JXYBJS",
"persistentURL": "https://doi.org/10.5072/FK2/JXYBJS",
"missingFileMetadata": [
{
@@ -6247,9 +6245,7 @@ Sample JSON Audit Response::
},
{
"id": 47731,
- "identifier": "DVN/MPU019",
- "authority": "10.7910",
- "protocol": "doi",
+ "pid": "doi:10.5072/FK2/MPU019",
"persistentURL": "https://doi.org/10.7910/DVN/MPU019",
"missingFiles": [
{
@@ -6262,7 +6258,7 @@ Sample JSON Audit Response::
],
"failures": [
{
- "datasetIdentifier": "doi.org/10.5072/FK2/XXXXXX",
+ "datasetIdentifier": "doi:10.5072/FK2/XXXXXX",
"reason": "Not Found"
}
]
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java
index 61f76c9928c..152bcf5066e 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/Admin.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/Admin.java
@@ -2641,9 +2641,7 @@ public Response getAuditFiles(@Context ContainerRequestContext crc,
JsonObjectBuilder job = Json.createObjectBuilder();
if (!missingFiles.isEmpty() || !missingFileMetadata.isEmpty()) {
job.add("id", dataset.getId());
- job.add("identifier", dataset.getIdentifier());
- job.add("authority", dataset.getAuthority());
- job.add("protocol", dataset.getProtocol());
+ job.add("pid", dataset.getProtocol() + ":" + dataset.getAuthority() + "/" + dataset.getIdentifier());
job.add("persistentURL", dataset.getPersistentURL());
if (!missingFileMetadata.isEmpty()) {
JsonArrayBuilder jabMissingFileMetadata = Json.createArrayBuilder();
From 2c5aca8e952b39fd1e1f7cfa99298303ca535799 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Wed, 20 Nov 2024 10:30:50 -0500
Subject: [PATCH 180/270] fix typos
---
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 9a5f469a4d0..9d6871041fd 100644
--- a/doc/sphinx-guides/source/api/native-api.rst
+++ b/doc/sphinx-guides/source/api/native-api.rst
@@ -6210,7 +6210,7 @@ Scans the Datasets in the database and verifies that the stored files exist. If
Optional Parameters are available for filtering the Datasets scanned.
-For auditing the Datasets in a paged manor (firstId and lastId)::
+For auditing the Datasets in a paged manner (firstId and lastId)::
curl "$SERVER_URL/api/admin/datafiles/auditFiles?firstId=0&lastId=1000"
From 3c67a7977fdaa862460e25208bb1bcb7a9c4a3c4 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Wed, 20 Nov 2024 10:31:15 -0500
Subject: [PATCH 181/270] Update
doc/release-notes/220-harvard-edu-audit-files.md
Co-authored-by: Philip Durbin
---
doc/release-notes/220-harvard-edu-audit-files.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/release-notes/220-harvard-edu-audit-files.md b/doc/release-notes/220-harvard-edu-audit-files.md
index c697bc225c0..79391703041 100644
--- a/doc/release-notes/220-harvard-edu-audit-files.md
+++ b/doc/release-notes/220-harvard-edu-audit-files.md
@@ -5,7 +5,7 @@ The Datasets scanned can be limited by optional firstId and lastId query paramet
Once the audit report is generated, a superuser can either delete the missing file(s) from the Dataset or contact the author to re-upload the missing file(s).
The JSON response includes:
-- List of files in each DataFile where the file exists in the database but the physical file is not on the file store.
+- List of files in each DataFile where the file exists in the database but the physical file is not in the file store.
- List of DataFiles where the FileMetadata is missing.
- Other failures found when trying to process the Datasets
From 58d32357978ddbae7abe587e5645fbc52192e471 Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Wed, 20 Nov 2024 10:31:24 -0500
Subject: [PATCH 182/270] Update
doc/release-notes/220-harvard-edu-audit-files.md
Co-authored-by: Philip Durbin
---
doc/release-notes/220-harvard-edu-audit-files.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/release-notes/220-harvard-edu-audit-files.md b/doc/release-notes/220-harvard-edu-audit-files.md
index 79391703041..002c8e85063 100644
--- a/doc/release-notes/220-harvard-edu-audit-files.md
+++ b/doc/release-notes/220-harvard-edu-audit-files.md
@@ -13,4 +13,4 @@ curl "http://localhost:8080/api/admin/datafiles/auditFiles
curl "http://localhost:8080/api/admin/datafiles/auditFiles?firstId=0&lastId=1000"
curl "http://localhost:8080/api/admin/datafiles/auditFiles?datasetIdentifierList=doi:10.5072/FK2/RVNT9Q,doi:10.5072/FK2/RVNT9Q
-For more information, see issue [the docs](https://dataverse-guide--11016.org.readthedocs.build/en/11016/api/native-api.html#datafile-audit), #11016, and [#220](https://github.com/IQSS/dataverse.harvard.edu/issues/220)
+For more information, see [the docs](https://dataverse-guide--11016.org.readthedocs.build/en/11016/api/native-api.html#datafile-audit), #11016, and [#220](https://github.com/IQSS/dataverse.harvard.edu/issues/220)
From a1f057287ee1297e8ddf5ab9c0f8ec94dbcf640f Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Wed, 20 Nov 2024 10:42:30 -0500
Subject: [PATCH 183/270] added a configurable batch size limit for when to
apply the single file size lookup method for the entire batch. #10977
---
.../iq/dataverse/globus/GlobusServiceBean.java | 16 ++++++++++------
.../dataverse/settings/SettingsServiceBean.java | 6 ++++++
.../harvard/iq/dataverse/util/SystemConfig.java | 6 ++++++
3 files changed, 22 insertions(+), 6 deletions(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/globus/GlobusServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/globus/GlobusServiceBean.java
index 3d1c5a1044d..5c9a2f1d946 100644
--- a/src/main/java/edu/harvard/iq/dataverse/globus/GlobusServiceBean.java
+++ b/src/main/java/edu/harvard/iq/dataverse/globus/GlobusServiceBean.java
@@ -986,11 +986,15 @@ private void processUploadedFiles(JsonArray filesJsonArray, Dataset dataset, Aut
inputList.add(fileId + "IDsplit" + fullPath + "IDsplit" + fileName);
}
- // Look up the sizes of all the files in the dataset folder, to avoid
- // looking them up one by one later:
- // @todo: we should only be doing this if this is a managed store, probably?
- GlobusEndpoint endpoint = getGlobusEndpoint(dataset);
- Map fileSizeMap = lookupFileSizes(endpoint, endpoint.getBasePath());
+ Map fileSizeMap = null;
+
+ if (filesJsonArray.size() >= systemConfig.getGlobusBatchLookupSize()) {
+ // Look up the sizes of all the files in the dataset folder, to avoid
+ // looking them up one by one later:
+ // @todo: we should only be doing this if this is a managed store, probably (?)
+ GlobusEndpoint endpoint = getGlobusEndpoint(dataset);
+ fileSizeMap = lookupFileSizes(endpoint, endpoint.getBasePath());
+ }
// calculateMissingMetadataFields: checksum, mimetype
JsonObject newfilesJsonObject = calculateMissingMetadataFields(inputList, myLogger);
@@ -1034,7 +1038,7 @@ private void processUploadedFiles(JsonArray filesJsonArray, Dataset dataset, Aut
.add("/fileSize", Json.createValue(uploadedFileSize)).build();
fileJsonObject = patch.apply(fileJsonObject);
} else {
- logger.warning("No file size entry found for file "+fileId);
+ logger.fine("No file size entry found for file "+fileId);
}
addFilesJsonData.add(fileJsonObject);
countSuccess++;
diff --git a/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java
index 8ed96690e84..b5eb483c2c8 100644
--- a/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java
+++ b/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java
@@ -539,6 +539,12 @@ Whether Harvesting (OAI) service is enabled
*
*/
GlobusSingleFileTransfer,
+ /** Lower limit of the number of files in a Globus upload task where
+ * the batch mode should be utilized in looking up the file information
+ * on the remote end node (file sizes, primarily), instead of individual
+ * lookups.
+ */
+ GlobusBatchLookupSize,
/**
* Optional external executables to run on the metadata for dataverses
* and datasets being published; as an extra validation step, to
diff --git a/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java b/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java
index 434b3bd8f8f..e769cacfdb1 100644
--- a/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java
+++ b/src/main/java/edu/harvard/iq/dataverse/util/SystemConfig.java
@@ -78,6 +78,7 @@ public class SystemConfig {
public static final long defaultZipDownloadLimit = 104857600L; // 100MB
private static final int defaultMultipleUploadFilesLimit = 1000;
private static final int defaultLoginSessionTimeout = 480; // = 8 hours
+ private static final int defaultGlobusBatchLookupSize = 50;
private String buildNumber = null;
@@ -954,6 +955,11 @@ public boolean isGlobusFileDownload() {
return (isGlobusDownload() && settingsService.isTrueForKey(SettingsServiceBean.Key.GlobusSingleFileTransfer, false));
}
+ public int getGlobusBatchLookupSize() {
+ String batchSizeOption = settingsService.getValueForKey(SettingsServiceBean.Key.GlobusBatchLookupSize);
+ return getIntLimitFromStringOrDefault(batchSizeOption, defaultGlobusBatchLookupSize);
+ }
+
private Boolean getMethodAvailable(String method, boolean upload) {
String methods = settingsService.getValueForKey(
upload ? SettingsServiceBean.Key.UploadMethods : SettingsServiceBean.Key.DownloadMethods);
From 50b752a0116d060de95b790a23a63840c90feb6c Mon Sep 17 00:00:00 2001
From: Steven Winship <39765413+stevenwinship@users.noreply.github.com>
Date: Wed, 20 Nov 2024 10:45:48 -0500
Subject: [PATCH 184/270] fix typos
---
doc/sphinx-guides/source/api/native-api.rst | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst
index 9d6871041fd..bfcbbb96f93 100644
--- a/doc/sphinx-guides/source/api/native-api.rst
+++ b/doc/sphinx-guides/source/api/native-api.rst
@@ -6204,7 +6204,8 @@ Datafile Audit
~~~~~~~~~~~~~~
Produce an audit report of missing files and FileMetadata for Datasets.
-Scans the Datasets in the database and verifies that the stored files exist. If the files are missing or if the FileMetadata is missing, this information is returned in a JSON response::
+Scans the Datasets in the database and verifies that the stored files exist. If the files are missing or if the FileMetadata is missing, this information is returned in a JSON response.
+The call will return a status code of 200 if the report was generated successfully. Issues found will be documented in the report and will not return a failure status code unless the report could not be generated::
curl "$SERVER_URL/api/admin/datafiles/auditFiles"
From 536c1bfe9466242797ce5a037936560d5cd0e197 Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Wed, 20 Nov 2024 12:08:21 -0500
Subject: [PATCH 185/270] release note #10977
---
doc/release-notes/10977-globus-filesize-lookup.md | 6 ++++++
1 file changed, 6 insertions(+)
create mode 100644 doc/release-notes/10977-globus-filesize-lookup.md
diff --git a/doc/release-notes/10977-globus-filesize-lookup.md b/doc/release-notes/10977-globus-filesize-lookup.md
new file mode 100644
index 00000000000..49fd10d9ffe
--- /dev/null
+++ b/doc/release-notes/10977-globus-filesize-lookup.md
@@ -0,0 +1,6 @@
+## A new Globus optimization setting
+
+An optimization has been added for the Globus upload workflow, with a corresponding new database setting: `:GlobusBatchLookupSize`
+
+
+See the [Database Settings](https://guides.dataverse.org/en/6.5/installation/config.html#GlobusBatchLookupSize) section of the Guides for more information.
\ No newline at end of file
From 617b13ae40fbed89a14816aa0c07cb10b228db6d Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Wed, 20 Nov 2024 12:11:25 -0500
Subject: [PATCH 186/270] configuration guide entry #10977
---
doc/sphinx-guides/source/installation/config.rst | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/doc/sphinx-guides/source/installation/config.rst b/doc/sphinx-guides/source/installation/config.rst
index e3965e3cd7c..30a36da9499 100644
--- a/doc/sphinx-guides/source/installation/config.rst
+++ b/doc/sphinx-guides/source/installation/config.rst
@@ -4849,6 +4849,13 @@ The URL where the `dataverse-globus
Date: Thu, 21 Nov 2024 10:57:37 +0000
Subject: [PATCH 187/270] Changed: docs and release note tweak for input levels
---
doc/release-notes/11018-update-dataverse-endpoint-update.md | 2 +-
doc/sphinx-guides/source/api/native-api.rst | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/doc/release-notes/11018-update-dataverse-endpoint-update.md b/doc/release-notes/11018-update-dataverse-endpoint-update.md
index dcd8eb0c90d..c2d9cf64af3 100644
--- a/doc/release-notes/11018-update-dataverse-endpoint-update.md
+++ b/doc/release-notes/11018-update-dataverse-endpoint-update.md
@@ -3,6 +3,6 @@ The updateDataverse API endpoint has been updated to support an "inherit from pa
When it comes to omitting any of these fields in the request JSON:
- Omitting ``facetIds`` or ``metadataBlockNames`` causes the Dataverse collection to inherit the corresponding configuration from its parent.
-- Omitting ``inputLevels`` removes any existing input levels in the Dataverse collection.
+- Omitting ``inputLevels`` removes any existing custom input levels in the Dataverse collection.
Previously, not setting these fields meant keeping the existing ones in the Dataverse.
diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst
index 9ac6fe196ff..cb3c7750961 100644
--- a/doc/sphinx-guides/source/api/native-api.rst
+++ b/doc/sphinx-guides/source/api/native-api.rst
@@ -129,7 +129,7 @@ Note that setting any of these fields overwrites the previous configuration.
When it comes to omitting these fields in the JSON:
- Omitting ``facetIds`` or ``metadataBlockNames`` causes the Dataverse collection to inherit the corresponding configuration from its parent.
-- Omitting ``inputLevels`` removes any existing input levels in the Dataverse collection.
+- Omitting ``inputLevels`` removes any existing custom input levels in the Dataverse collection.
To obtain an example of how these objects are included in the JSON file, download :download:`dataverse-complete-optional-params.json <../_static/api/dataverse-complete-optional-params.json>` file and modify it to suit your needs.
From c7da932cabfc3b395002bcbf373da4542b808a48 Mon Sep 17 00:00:00 2001
From: GPortas
Date: Thu, 21 Nov 2024 11:05:44 +0000
Subject: [PATCH 188/270] Added: doc tweak related to excluding metadataBlocks
in updateDataverse
---
doc/sphinx-guides/source/api/native-api.rst | 1 +
1 file changed, 1 insertion(+)
diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst
index 351688e2731..4689a46b40b 100644
--- a/doc/sphinx-guides/source/api/native-api.rst
+++ b/doc/sphinx-guides/source/api/native-api.rst
@@ -130,6 +130,7 @@ When it comes to omitting these fields in the JSON:
- Omitting ``facetIds`` or ``metadataBlockNames`` causes the Dataverse collection to inherit the corresponding configuration from its parent.
- Omitting ``inputLevels`` removes any existing custom input levels in the Dataverse collection.
+- Omitting the entire ``metadataBlocks`` object in the request JSON would exclude the three sub-objects, resulting in the application of the two changes described above.
To obtain an example of how these objects are included in the JSON file, download :download:`dataverse-complete-optional-params.json <../_static/api/dataverse-complete-optional-params.json>` file and modify it to suit your needs.
From b63b1ffe9bb6c511d7445ccd2cc451bb44a39815 Mon Sep 17 00:00:00 2001
From: GPortas
Date: Thu, 21 Nov 2024 11:09:02 +0000
Subject: [PATCH 189/270] Added: doc tweak explaining metadataBlocks is
optional in updateDataverse endpoint
---
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 4689a46b40b..e542ad8bafd 100644
--- a/doc/sphinx-guides/source/api/native-api.rst
+++ b/doc/sphinx-guides/source/api/native-api.rst
@@ -118,7 +118,7 @@ The fully expanded example above (without environment variables) looks like this
You should expect an HTTP 200 response and JSON beginning with "status":"OK" followed by a representation of the updated Dataverse collection.
-Same as in :ref:`create-dataverse-api`, the request JSON supports a ``metadataBlocks`` object, with the following supported sub-objects:
+Same as in :ref:`create-dataverse-api`, the request JSON supports an optional ``metadataBlocks`` object, with the following supported sub-objects:
- ``metadataBlockNames``: The names of the metadata blocks to be assigned to the Dataverse collection.
- ``inputLevels``: The names of the fields in each metadata block for which you want to add a custom configuration regarding their inclusion or requirement when creating and editing datasets in the Dataverse collection. Note that if the corresponding metadata blocks names are not specified in the ``metadataBlockNames``` field, they will be added automatically to the Dataverse collection.
From 4449679d2af3614de54a077aaaae4ea2551ed22b Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Mon, 21 Oct 2024 17:14:19 -0400
Subject: [PATCH 190/270] quick draft implementation of addressing issue 1.
from #10909.
---
.../api/imports/ImportGenericServiceBean.java | 41 +++++++++++++++----
.../api/imports/ImportServiceBean.java | 13 +++++-
.../harvest/client/HarvestingClient.java | 23 ++++++++++-
src/main/resources/db/migration/V6.4.0.1.sql | 2 +-
4 files changed, 65 insertions(+), 14 deletions(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportGenericServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportGenericServiceBean.java
index 41a57665010..bf8d068a69c 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportGenericServiceBean.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportGenericServiceBean.java
@@ -150,12 +150,16 @@ public DatasetDTO processXML( XMLStreamReader xmlr, ForeignMetadataFormatMapping
}
- // Helper method for importing harvested Dublin Core xml.
+ // Helper methods for importing harvested Dublin Core xml.
// Dublin Core is considered a mandatory, built in metadata format mapping.
// It is distributed as required content, in reference_data.sql.
// Note that arbitrary formatting tags are supported for the outer xml
// wrapper. -- L.A. 4.5
public DatasetDTO processOAIDCxml(String DcXmlToParse) throws XMLStreamException {
+ return processOAIDCxml(DcXmlToParse, null);
+ }
+
+ public DatasetDTO processOAIDCxml(String DcXmlToParse, String oaiIdentifier) throws XMLStreamException {
// look up DC metadata mapping:
ForeignMetadataFormatMapping dublinCoreMapping = findFormatMappingByName(DCTERMS);
@@ -185,18 +189,37 @@ public DatasetDTO processOAIDCxml(String DcXmlToParse) throws XMLStreamException
datasetDTO.getDatasetVersion().setVersionState(DatasetVersion.VersionState.RELEASED);
- // Our DC import handles the contents of the dc:identifier field
- // as an "other id". In the context of OAI harvesting, we expect
- // the identifier to be a global id, so we need to rearrange that:
+ // In some cases, the identifier that we want to use for the dataset is
+ // already supplied to the method explicitly. For example, in some
+ // harvesting cases we'll want to use the OAI identifier (the identifier
+ // from the section of the OAI record) for that purpose, without
+ // expecting to find a valid persistent id in the body of the DC record:
- String identifier = getOtherIdFromDTO(datasetDTO.getDatasetVersion());
- logger.fine("Imported identifier: "+identifier);
+ String globalIdentifier;
- String globalIdentifier = reassignIdentifierAsGlobalId(identifier, datasetDTO);
- logger.fine("Detected global identifier: "+globalIdentifier);
+ if (oaiIdentifier != null) {
+ logger.fine("Attempting to use " + oaiIdentifier + " as the persistentId of the imported dataset");
+
+ globalIdentifier = reassignIdentifierAsGlobalId(oaiIdentifier, datasetDTO);
+ } else {
+ // Our DC import handles the contents of the dc:identifier field
+ // as an "other id". Unless we are using an externally supplied
+ // global id, we will be using the first such "other id" that we
+ // can parse and recognize as the global id for the imported dataset
+ // (note that this is the default behavior during harvesting),
+ // so we need to reaassign it accordingly:
+ String identifier = getOtherIdFromDTO(datasetDTO.getDatasetVersion());
+ logger.fine("Imported identifier: " + identifier);
+
+ globalIdentifier = reassignIdentifierAsGlobalId(identifier, datasetDTO);
+ logger.fine("Detected global identifier: " + globalIdentifier);
+ }
if (globalIdentifier == null) {
- throw new EJBException("Failed to find a global identifier in the OAI_DC XML record.");
+ String exceptionMsg = oaiIdentifier == null ?
+ "Failed to find a global identifier in the OAI_DC XML record." :
+ "Failed to parse the supplied identifier as a valid Persistent Id";
+ throw new EJBException(exceptionMsg);
}
return datasetDTO;
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java
index ee4609a7c56..d0a0629e1ae 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java
@@ -208,7 +208,13 @@ public JsonObjectBuilder handleFile(DataverseRequest dataverseRequest, Dataverse
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
- public Dataset doImportHarvestedDataset(DataverseRequest dataverseRequest, HarvestingClient harvestingClient, String harvestIdentifier, String metadataFormat, File metadataFile, Date oaiDateStamp, PrintWriter cleanupLog) throws ImportException, IOException {
+ public Dataset doImportHarvestedDataset(DataverseRequest dataverseRequest,
+ HarvestingClient harvestingClient,
+ String harvestIdentifier,
+ String metadataFormat,
+ File metadataFile,
+ Date oaiDateStamp,
+ PrintWriter cleanupLog) throws ImportException, IOException {
if (harvestingClient == null || harvestingClient.getDataverse() == null) {
throw new ImportException("importHarvestedDataset called with a null harvestingClient, or an invalid harvestingClient.");
}
@@ -245,7 +251,10 @@ public Dataset doImportHarvestedDataset(DataverseRequest dataverseRequest, Harve
logger.fine("importing DC "+metadataFile.getAbsolutePath());
try {
String xmlToParse = new String(Files.readAllBytes(metadataFile.toPath()));
- dsDTO = importGenericService.processOAIDCxml(xmlToParse);
+ String suggestedIdentifier = harvestingClient.isUseOaiIdentifiersAsPids()
+ ? harvestIdentifier
+ : null;
+ dsDTO = importGenericService.processOAIDCxml(xmlToParse, suggestedIdentifier);
} catch (IOException | XMLStreamException e) {
throw new ImportException("Failed to process Dublin Core XML record: "+ e.getClass() + " (" + e.getMessage() + ")");
}
diff --git a/src/main/java/edu/harvard/iq/dataverse/harvest/client/HarvestingClient.java b/src/main/java/edu/harvard/iq/dataverse/harvest/client/HarvestingClient.java
index 0667f5594ce..ec26729b685 100644
--- a/src/main/java/edu/harvard/iq/dataverse/harvest/client/HarvestingClient.java
+++ b/src/main/java/edu/harvard/iq/dataverse/harvest/client/HarvestingClient.java
@@ -214,6 +214,7 @@ public void setArchiveDescription(String archiveDescription) {
this.archiveDescription = archiveDescription;
}
+ @Column(columnDefinition="TEXT")
private String harvestingSet;
public String getHarvestingSet() {
@@ -252,8 +253,26 @@ public void setAllowHarvestingMissingCVV(boolean allowHarvestingMissingCVV) {
this.allowHarvestingMissingCVV = allowHarvestingMissingCVV;
}
- // TODO: do we need "orphanRemoval=true"? -- L.A. 4.4
- // TODO: should it be @OrderBy("startTime")? -- L.A. 4.4
+ private Boolean useListRecords;
+
+ public Boolean isUseListRecords() {
+ return useListRecords;
+ }
+
+ public void setUseListrecords(boolean useListRecords) {
+ this.useListRecords = useListRecords;
+ }
+
+ private Boolean useOaiIdAsPid;
+
+ public Boolean isUseOaiIdentifiersAsPids() {
+ return useOaiIdAsPid;
+ }
+
+ public void setUseOaiIdentifiersAsPids(boolean useOaiIdAsPid) {
+ this.useOaiIdAsPid = useOaiIdAsPid;
+ }
+
@OneToMany(mappedBy="harvestingClient", cascade={CascadeType.REMOVE, CascadeType.MERGE, CascadeType.PERSIST})
@OrderBy("id")
private List harvestHistory;
diff --git a/src/main/resources/db/migration/V6.4.0.1.sql b/src/main/resources/db/migration/V6.4.0.1.sql
index 0bcd87dd736..438c52a192e 100644
--- a/src/main/resources/db/migration/V6.4.0.1.sql
+++ b/src/main/resources/db/migration/V6.4.0.1.sql
@@ -1,4 +1,4 @@
-- Adding a case-insensitive index related to #11003
--
-CREATE UNIQUE INDEX IF NOT EXISTS INDEX_DVOBJECT_authority_protocol_upper_identifier ON dvobject (authority, protocol, UPPER(identifier));
\ No newline at end of file
+CREATE UNIQUE INDEX IF NOT EXISTS INDEX_DVOBJECT_authority_protocol_upper_identifier ON dvobject (authority, protocol, UPPER(identifier));
From 2656ccdcc0c7f59eebfcf52e82c011c829b7dda7 Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Tue, 29 Oct 2024 10:27:09 -0400
Subject: [PATCH 191/270] Adding the new client options to the json printer and
parser #10909
---
.../java/edu/harvard/iq/dataverse/util/json/JsonParser.java | 2 ++
.../java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java | 2 ++
2 files changed, 4 insertions(+)
diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java
index 3f60317655a..8bb8fd93dd1 100644
--- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java
+++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java
@@ -1052,6 +1052,8 @@ public String parseHarvestingClient(JsonObject obj, HarvestingClient harvestingC
harvestingClient.setHarvestingSet(obj.getString("set",null));
harvestingClient.setCustomHttpHeaders(obj.getString("customHeaders", null));
harvestingClient.setAllowHarvestingMissingCVV(obj.getBoolean("allowHarvestingMissingCVV", false));
+ harvestingClient.setUseListrecords(obj.getBoolean("useListRecords", false));
+ harvestingClient.setUseOaiIdentifiersAsPids(obj.getBoolean("useOaiIdentifiersAsPids", false));
return dataverseAlias;
}
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 f884d313d64..6666a7f0e7d 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
@@ -1013,6 +1013,8 @@ public static JsonObjectBuilder json(HarvestingClient harvestingClient) {
add("status", harvestingClient.isHarvestingNow() ? "inProgress" : "inActive").
add("customHeaders", harvestingClient.getCustomHttpHeaders()).
add("allowHarvestingMissingCVV", harvestingClient.getAllowHarvestingMissingCVV()).
+ add("useListRecords", harvestingClient.isUseListRecords()).
+ add("useOaiIdentifiersAsPids", harvestingClient.isUseOaiIdentifiersAsPids()).
add("lastHarvest", harvestingClient.getLastHarvestTime() == null ? null : harvestingClient.getLastHarvestTime().toString()).
add("lastResult", harvestingClient.getLastResult()).
add("lastSuccessful", harvestingClient.getLastSuccessfulHarvestTime() == null ? null : harvestingClient.getLastSuccessfulHarvestTime().toString()).
From 5c043cdb642bc9fa17cc0620292b11004813ed56 Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Fri, 1 Nov 2024 10:26:39 -0400
Subject: [PATCH 192/270] we DO want to include the persistent id in the search
cards for all harvested datasets. #10909. (that whole block of extra checks
on the harvest "style" may be redundant by now - I'll think about it)
---
src/main/java/edu/harvard/iq/dataverse/DataCitation.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/main/java/edu/harvard/iq/dataverse/DataCitation.java b/src/main/java/edu/harvard/iq/dataverse/DataCitation.java
index 3977023fc4b..02fb59751fb 100644
--- a/src/main/java/edu/harvard/iq/dataverse/DataCitation.java
+++ b/src/main/java/edu/harvard/iq/dataverse/DataCitation.java
@@ -792,6 +792,7 @@ private GlobalId getPIDFrom(DatasetVersion dsv, DvObject dv) {
if (!dsv.getDataset().isHarvested()
|| HarvestingClient.HARVEST_STYLE_VDC.equals(dsv.getDataset().getHarvestedFrom().getHarvestStyle())
|| HarvestingClient.HARVEST_STYLE_ICPSR.equals(dsv.getDataset().getHarvestedFrom().getHarvestStyle())
+ || HarvestingClient.HARVEST_STYLE_DEFAULT.equals(dsv.getDataset().getHarvestedFrom().getHarvestStyle())
|| HarvestingClient.HARVEST_STYLE_DATAVERSE
.equals(dsv.getDataset().getHarvestedFrom().getHarvestStyle())) {
if(!isDirect()) {
From b7efee0cfbd45104c5432b69a2732def5889b868 Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Fri, 22 Nov 2024 19:50:21 -0500
Subject: [PATCH 193/270] a flyway script for the "use the oai id as the pid"
harvesting client flag.
---
src/main/resources/db/migration/V6.4.0.3.sql | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 src/main/resources/db/migration/V6.4.0.3.sql
diff --git a/src/main/resources/db/migration/V6.4.0.3.sql b/src/main/resources/db/migration/V6.4.0.3.sql
new file mode 100644
index 00000000000..307d8ed206c
--- /dev/null
+++ b/src/main/resources/db/migration/V6.4.0.3.sql
@@ -0,0 +1,2 @@
+-- Add this boolean flag to accommodate a new harvesting client feature
+ALTER TABLE harvestingclient ADD COLUMN IF NOT EXISTS useOaiIdAsPid BOOLEAN DEFAULT FALSE;
From eca03896f3d0a1e36505ea958e7dc77c118d62c5 Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Fri, 22 Nov 2024 19:53:25 -0500
Subject: [PATCH 194/270] removed the part of the cherry-picked commit that I'm
not going to need in this branch.
---
.../dataverse/harvest/client/HarvestingClient.java | 14 ++------------
1 file changed, 2 insertions(+), 12 deletions(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/harvest/client/HarvestingClient.java b/src/main/java/edu/harvard/iq/dataverse/harvest/client/HarvestingClient.java
index ec26729b685..de9cc7c0db6 100644
--- a/src/main/java/edu/harvard/iq/dataverse/harvest/client/HarvestingClient.java
+++ b/src/main/java/edu/harvard/iq/dataverse/harvest/client/HarvestingClient.java
@@ -253,19 +253,9 @@ public void setAllowHarvestingMissingCVV(boolean allowHarvestingMissingCVV) {
this.allowHarvestingMissingCVV = allowHarvestingMissingCVV;
}
- private Boolean useListRecords;
+ private boolean useOaiIdAsPid;
- public Boolean isUseListRecords() {
- return useListRecords;
- }
-
- public void setUseListrecords(boolean useListRecords) {
- this.useListRecords = useListRecords;
- }
-
- private Boolean useOaiIdAsPid;
-
- public Boolean isUseOaiIdentifiersAsPids() {
+ public boolean isUseOaiIdentifiersAsPids() {
return useOaiIdAsPid;
}
From 29114175c316dda01e0e240d63951cb89a4f9fdd Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Fri, 22 Nov 2024 19:55:40 -0500
Subject: [PATCH 195/270] removed pieces of another cherry-picked commit not
needed in this branch.
---
src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java | 1 -
.../java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java | 1 -
2 files changed, 2 deletions(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java
index 8bb8fd93dd1..232b7431a24 100644
--- a/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java
+++ b/src/main/java/edu/harvard/iq/dataverse/util/json/JsonParser.java
@@ -1052,7 +1052,6 @@ public String parseHarvestingClient(JsonObject obj, HarvestingClient harvestingC
harvestingClient.setHarvestingSet(obj.getString("set",null));
harvestingClient.setCustomHttpHeaders(obj.getString("customHeaders", null));
harvestingClient.setAllowHarvestingMissingCVV(obj.getBoolean("allowHarvestingMissingCVV", false));
- harvestingClient.setUseListrecords(obj.getBoolean("useListRecords", false));
harvestingClient.setUseOaiIdentifiersAsPids(obj.getBoolean("useOaiIdentifiersAsPids", false));
return dataverseAlias;
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 6666a7f0e7d..91af13c79a3 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
@@ -1013,7 +1013,6 @@ public static JsonObjectBuilder json(HarvestingClient harvestingClient) {
add("status", harvestingClient.isHarvestingNow() ? "inProgress" : "inActive").
add("customHeaders", harvestingClient.getCustomHttpHeaders()).
add("allowHarvestingMissingCVV", harvestingClient.getAllowHarvestingMissingCVV()).
- add("useListRecords", harvestingClient.isUseListRecords()).
add("useOaiIdentifiersAsPids", harvestingClient.isUseOaiIdentifiersAsPids()).
add("lastHarvest", harvestingClient.getLastHarvestTime() == null ? null : harvestingClient.getLastHarvestTime().toString()).
add("lastResult", harvestingClient.getLastResult()).
From 0967b7a8363381d8e6f5c52a230215f87cf4f671 Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Fri, 22 Nov 2024 20:20:35 -0500
Subject: [PATCH 196/270] A "hybrid" implementation of the support for using
OAI identifiers for the pid of the imported datasets - merging 2 different
approaches implemented in the PRs
---
.../api/imports/ImportGenericServiceBean.java | 26 ++++++++++++++++---
.../api/imports/ImportServiceBean.java | 7 ++---
2 files changed, 24 insertions(+), 9 deletions(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportGenericServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportGenericServiceBean.java
index bf8d068a69c..7bce0947a0e 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportGenericServiceBean.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportGenericServiceBean.java
@@ -156,10 +156,10 @@ public DatasetDTO processXML( XMLStreamReader xmlr, ForeignMetadataFormatMapping
// Note that arbitrary formatting tags are supported for the outer xml
// wrapper. -- L.A. 4.5
public DatasetDTO processOAIDCxml(String DcXmlToParse) throws XMLStreamException {
- return processOAIDCxml(DcXmlToParse, null);
+ return processOAIDCxml(DcXmlToParse, null, false);
}
- public DatasetDTO processOAIDCxml(String DcXmlToParse, String oaiIdentifier) throws XMLStreamException {
+ public DatasetDTO processOAIDCxml(String DcXmlToParse, String oaiIdentifier, boolean preferSuppliedIdentifier) throws XMLStreamException {
// look up DC metadata mapping:
ForeignMetadataFormatMapping dublinCoreMapping = findFormatMappingByName(DCTERMS);
@@ -208,7 +208,7 @@ public DatasetDTO processOAIDCxml(String DcXmlToParse, String oaiIdentifier) thr
// can parse and recognize as the global id for the imported dataset
// (note that this is the default behavior during harvesting),
// so we need to reaassign it accordingly:
- String identifier = getOtherIdFromDTO(datasetDTO.getDatasetVersion());
+ String identifier = selectIdentifier(datasetDTO.getDatasetVersion(), oaiIdentifier, preferSuppliedIdentifier);
logger.fine("Imported identifier: " + identifier);
globalIdentifier = reassignIdentifierAsGlobalId(identifier, datasetDTO);
@@ -367,8 +367,16 @@ private FieldDTO makeDTO(DatasetFieldType dataverseFieldType, FieldDTO value, St
return value;
}
- private String getOtherIdFromDTO(DatasetVersionDTO datasetVersionDTO) {
+ private String selectIdentifier(DatasetVersionDTO datasetVersionDTO, String suppliedIdentifier, boolean preferSuppliedIdentifier) {
List otherIds = new ArrayList<>();
+
+ if (suppliedIdentifier != null && preferSuppliedIdentifier) {
+ // This supplied identifier (in practice, his is likely the OAI-PMH
+ // identifier from the section) will be our first
+ // choice candidate for the pid of the imported dataset:
+ otherIds.add(suppliedIdentifier);
+ }
+
for (Map.Entry entry : datasetVersionDTO.getMetadataBlocks().entrySet()) {
String key = entry.getKey();
MetadataBlockDTO value = entry.getValue();
@@ -386,6 +394,16 @@ private String getOtherIdFromDTO(DatasetVersionDTO datasetVersionDTO) {
}
}
}
+
+ if (suppliedIdentifier != null && !preferSuppliedIdentifier) {
+ // Unless specifically instructed to prefer this extra identifier
+ // (in practice, this is likely the OAI-PMH identifier from the
+ // section), we will try to use it as the *last*
+ // possible candidate for the pid, so, adding it to the end of the
+ // list:
+ otherIds.add(suppliedIdentifier);
+ }
+
if (!otherIds.isEmpty()) {
// We prefer doi or hdl identifiers like "doi:10.7910/DVN/1HE30F"
for (String otherId : otherIds) {
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java
index d0a0629e1ae..7dc2aed799e 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportServiceBean.java
@@ -250,11 +250,8 @@ public Dataset doImportHarvestedDataset(DataverseRequest dataverseRequest,
} else if ("dc".equalsIgnoreCase(metadataFormat) || "oai_dc".equals(metadataFormat)) {
logger.fine("importing DC "+metadataFile.getAbsolutePath());
try {
- String xmlToParse = new String(Files.readAllBytes(metadataFile.toPath()));
- String suggestedIdentifier = harvestingClient.isUseOaiIdentifiersAsPids()
- ? harvestIdentifier
- : null;
- dsDTO = importGenericService.processOAIDCxml(xmlToParse, suggestedIdentifier);
+ String xmlToParse = new String(Files.readAllBytes(metadataFile.toPath()));
+ dsDTO = importGenericService.processOAIDCxml(xmlToParse, harvestIdentifier, harvestingClient.isUseOaiIdentifiersAsPids());
} catch (IOException | XMLStreamException e) {
throw new ImportException("Failed to process Dublin Core XML record: "+ e.getClass() + " (" + e.getMessage() + ")");
}
From 00943e1e2a6ce9825dbc29d898431664cad07498 Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Sun, 24 Nov 2024 20:35:27 -0500
Subject: [PATCH 197/270] guide entry
---
doc/sphinx-guides/source/api/native-api.rst | 2 ++
1 file changed, 2 insertions(+)
diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst
index 99a8a9f7cf4..1f36691be0d 100644
--- a/doc/sphinx-guides/source/api/native-api.rst
+++ b/doc/sphinx-guides/source/api/native-api.rst
@@ -5246,6 +5246,7 @@ Shows a Harvesting Client with a defined nickname::
"dataverseAlias": "fooData",
"nickName": "myClient",
"set": "fooSet",
+ "useOaiIdentifiersAsPids": false
"schedule": "none",
"status": "inActive",
"lastHarvest": "Thu Oct 13 14:48:57 EDT 2022",
@@ -5280,6 +5281,7 @@ The following optional fields are supported:
- style: Defaults to "default" - a generic OAI archive. (Make sure to use "dataverse" when configuring harvesting from another Dataverse installation).
- customHeaders: This can be used to configure this client with a specific HTTP header that will be added to every OAI request. This is to accommodate a use case where the remote server requires this header to supply some form of a token in order to offer some content not available to other clients. See the example below. Multiple headers can be supplied separated by `\\n` - actual "backslash" and "n" characters, not a single "new line" character.
- allowHarvestingMissingCVV: Flag to allow datasets to be harvested with Controlled Vocabulary Values that existed in the originating Dataverse Project but are not in the harvesting Dataverse Project. (Default is false). Currently only settable using API.
+- useOaiIdentifiersAsPids: Defaults to false; if set to true, Harvester will attempt to use the identifier from the OAI-PMH record header as the **first choice** for the persistent id of the harvested dataset. When set to false, Dataverse will still attempt to use this identifier, but only if none of the `` entries in the OAI_DC record contain a valid persistent id (this is new as of v6.5).
Generally, the API will accept the output of the GET version of the API for an existing client as valid input, but some fields will be ignored. For example, as of writing this there is no way to configure a harvesting schedule via this API.
From cc7fb45d43f8b842d079d0a688fc2487bd0f56f8 Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Sun, 24 Nov 2024 20:54:32 -0500
Subject: [PATCH 198/270] release note.
---
doc/release-notes/11049-oai-identifiers-as-pids.md | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 doc/release-notes/11049-oai-identifiers-as-pids.md
diff --git a/doc/release-notes/11049-oai-identifiers-as-pids.md b/doc/release-notes/11049-oai-identifiers-as-pids.md
new file mode 100644
index 00000000000..2f857bf2198
--- /dev/null
+++ b/doc/release-notes/11049-oai-identifiers-as-pids.md
@@ -0,0 +1,5 @@
+## When harvesting, Dataverse can now use the identifier from the OAI-PMH record header as the persistent id for the harvested dataset.
+
+This will allow harvesting from sources that do not include a persistent id in their oai_dc metadata records, but use valid dois or handles as the OAI-PMH record header identifiers.
+
+It is also possible to optionally configure a harvesting client to use this OAI-PMH identifier as the **preferred** choice for the persistent id. See the [Harvesting Clients API](https://guides.dataverse.org/en/6.5/api/native-api.html#create-a-harvesting-client) section of the Guides for more information.
\ No newline at end of file
From d6fc24022d4cd0cd6759064b7dd01e6885ad7c12 Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Sun, 24 Nov 2024 21:12:58 -0500
Subject: [PATCH 199/270] json files for the new tests (from PR #11010 by
@stevenferey)
---
.../json/importGenericWithOtherId.json | 307 ++++++++++++++++++
.../json/importGenericWithoutOtherId.json | 258 +++++++++++++++
2 files changed, 565 insertions(+)
create mode 100644 src/test/resources/json/importGenericWithOtherId.json
create mode 100644 src/test/resources/json/importGenericWithoutOtherId.json
diff --git a/src/test/resources/json/importGenericWithOtherId.json b/src/test/resources/json/importGenericWithOtherId.json
new file mode 100644
index 00000000000..af9241393e9
--- /dev/null
+++ b/src/test/resources/json/importGenericWithOtherId.json
@@ -0,0 +1,307 @@
+{
+ "UNF": "UNF",
+ "createTime": "2014-11-12 12:17:55 -05",
+ "distributionDate": "Distribution Date",
+ "id": 2,
+ "lastUpdateTime": "2014-11-12 12:20:32 -05",
+ "metadataBlocks": {
+ "astrophysics": {
+ "displayName": "Astronomy and Astrophysics Metadata",
+ "fields": [
+ {
+ "multiple": true,
+ "typeClass": "controlledVocabulary",
+ "typeName": "astroType",
+ "value": [
+ "Image",
+ "Mosaic",
+ "EventList"
+ ]
+ }
+ ]
+ },
+ "citation": {
+ "displayName": "Citation Metadata",
+ "fields": [
+ {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "title",
+ "value": "My Dataset"
+ },
+ {
+ "multiple": true,
+ "typeClass": "compound",
+ "typeName": "author",
+ "value": [
+ {
+ "authorAffiliation": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "authorAffiliation",
+ "value": "Top"
+ },
+ "authorIdentifier": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "authorIdentifier",
+ "value": "ellenid"
+ },
+ "authorIdentifierScheme": {
+ "multiple": false,
+ "typeClass": "controlledVocabulary",
+ "typeName": "authorIdentifierScheme",
+ "value": "ORCID"
+ },
+ "authorName": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "authorName",
+ "value": "Privileged, Pete"
+ }
+ },
+ {
+ "authorAffiliation": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "authorAffiliation",
+ "value": "Bottom"
+ },
+ "authorIdentifier": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "authorIdentifier",
+ "value": "audreyId"
+ },
+ "authorIdentifierScheme": {
+ "multiple": false,
+ "typeClass": "controlledVocabulary",
+ "typeName": "authorIdentifierScheme",
+ "value": "DAISY"
+ },
+ "authorName": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "authorName",
+ "value": "Awesome, Audrey"
+ }
+ }
+ ]
+ },
+ {
+ "multiple": true,
+ "typeClass": "primitive",
+ "typeName": "datasetContact",
+ "value": [
+ "pete@malinator.com"
+ ]
+ },
+ {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "dsDescription",
+ "value": "Here is my description"
+ },
+ {
+ "multiple": true,
+ "typeClass": "controlledVocabulary",
+ "typeName": "subject",
+ "value": [
+ "Arts and Humanities",
+ "Astronomy and Astrophysics",
+ "Business and Management"
+ ]
+ },
+ {
+ "multiple": true,
+ "typeClass": "primitive",
+ "typeName": "keyword",
+ "value": [
+ "keyword1",
+ "keyword2"
+ ]
+ },
+ {
+ "multiple": true,
+ "typeClass": "compound",
+ "typeName": "otherId",
+ "value": [
+ {
+ "otherIdAgency": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "otherIdAgency",
+ "value": "my agency"
+ },
+ "otherIdValue": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "otherIdValue",
+ "value": "otherId"
+ }
+ },
+ {
+ "otherIdAgency": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "otherIdAgency",
+ "value": "another agency"
+ },
+ "otherIdValue": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "otherIdValue",
+ "value": "otherId2"
+ }
+ },
+ {
+ "otherIdAgency": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "otherIdAgency",
+ "value": "another agency"
+ },
+ "otherIdValue": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "otherIdValue",
+ "value": "doi:10.7910/DVN/TJCLKP"
+ }
+ }
+ ]
+ },
+ {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "depositor",
+ "value": "Ellen K"
+ },
+ {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "dateOfDeposit",
+ "value": "2014-11-12"
+ }
+ ]
+ },
+ "geospatial": {
+ "displayName": "Geospatial Metadata",
+ "fields": [
+ {
+ "multiple": true,
+ "typeClass": "compound",
+ "typeName": "geographicCoverage",
+ "value": [
+ {
+ "city": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "city",
+ "value": "Arlington"
+ },
+ "country": {
+ "multiple": false,
+ "typeClass": "controlledVocabulary",
+ "typeName": "country",
+ "value": "United States"
+ },
+ "state": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "state",
+ "value": "MA"
+ }
+ },
+ {
+ "city": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "city",
+ "value": "beachcity"
+ },
+ "country": {
+ "multiple": false,
+ "typeClass": "controlledVocabulary",
+ "typeName": "country",
+ "value": "Aruba"
+ },
+ "state": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "state",
+ "value": "beach"
+ }
+ }
+ ]
+ },
+ {
+ "multiple": false,
+ "typeClass": "compound",
+ "typeName": "geographicBoundingBox",
+ "value":
+ {
+ "eastLongitude": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "eastLongitude",
+ "value": "23"
+ },
+ "northLatitude": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "northLatitude",
+ "value": "786"
+ },
+ "southLatitude": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "southLatitude",
+ "value": "34"
+ },
+ "westLongitude": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "westLongitude",
+ "value": "45"
+ }
+ }
+
+ }
+ ]
+ },
+ "socialscience": {
+ "displayName": "Social Science and Humanities Metadata",
+ "fields": [
+ {
+ "multiple": true,
+ "typeClass": "compound",
+ "typeName": "software",
+ "value": [
+ {
+ "softwareName": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "softwareName",
+ "value": "softwareName"
+ },
+ "softwareVersion": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "softwareVersion",
+ "value": "software version"
+ }
+ }
+ ]
+ },
+ {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "unitOfAnalysis",
+ "value": "unit of analysis"
+ }
+ ]
+ }
+ },
+ "productionDate": "Production Date",
+ "versionState": "DRAFT"
+ }
diff --git a/src/test/resources/json/importGenericWithoutOtherId.json b/src/test/resources/json/importGenericWithoutOtherId.json
new file mode 100644
index 00000000000..ceb2263c2cf
--- /dev/null
+++ b/src/test/resources/json/importGenericWithoutOtherId.json
@@ -0,0 +1,258 @@
+{
+ "UNF": "UNF",
+ "createTime": "2014-11-12 12:17:55 -05",
+ "distributionDate": "Distribution Date",
+ "id": 2,
+ "lastUpdateTime": "2014-11-12 12:20:32 -05",
+ "metadataBlocks": {
+ "astrophysics": {
+ "displayName": "Astronomy and Astrophysics Metadata",
+ "fields": [
+ {
+ "multiple": true,
+ "typeClass": "controlledVocabulary",
+ "typeName": "astroType",
+ "value": [
+ "Image",
+ "Mosaic",
+ "EventList"
+ ]
+ }
+ ]
+ },
+ "citation": {
+ "displayName": "Citation Metadata",
+ "fields": [
+ {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "title",
+ "value": "My Dataset"
+ },
+ {
+ "multiple": true,
+ "typeClass": "compound",
+ "typeName": "author",
+ "value": [
+ {
+ "authorAffiliation": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "authorAffiliation",
+ "value": "Top"
+ },
+ "authorIdentifier": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "authorIdentifier",
+ "value": "ellenid"
+ },
+ "authorIdentifierScheme": {
+ "multiple": false,
+ "typeClass": "controlledVocabulary",
+ "typeName": "authorIdentifierScheme",
+ "value": "ORCID"
+ },
+ "authorName": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "authorName",
+ "value": "Privileged, Pete"
+ }
+ },
+ {
+ "authorAffiliation": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "authorAffiliation",
+ "value": "Bottom"
+ },
+ "authorIdentifier": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "authorIdentifier",
+ "value": "audreyId"
+ },
+ "authorIdentifierScheme": {
+ "multiple": false,
+ "typeClass": "controlledVocabulary",
+ "typeName": "authorIdentifierScheme",
+ "value": "DAISY"
+ },
+ "authorName": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "authorName",
+ "value": "Awesome, Audrey"
+ }
+ }
+ ]
+ },
+ {
+ "multiple": true,
+ "typeClass": "primitive",
+ "typeName": "datasetContact",
+ "value": [
+ "pete@malinator.com"
+ ]
+ },
+ {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "dsDescription",
+ "value": "Here is my description"
+ },
+ {
+ "multiple": true,
+ "typeClass": "controlledVocabulary",
+ "typeName": "subject",
+ "value": [
+ "Arts and Humanities",
+ "Astronomy and Astrophysics",
+ "Business and Management"
+ ]
+ },
+ {
+ "multiple": true,
+ "typeClass": "primitive",
+ "typeName": "keyword",
+ "value": [
+ "keyword1",
+ "keyword2"
+ ]
+ },
+ {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "depositor",
+ "value": "Ellen K"
+ },
+ {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "dateOfDeposit",
+ "value": "2014-11-12"
+ }
+ ]
+ },
+ "geospatial": {
+ "displayName": "Geospatial Metadata",
+ "fields": [
+ {
+ "multiple": true,
+ "typeClass": "compound",
+ "typeName": "geographicCoverage",
+ "value": [
+ {
+ "city": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "city",
+ "value": "Arlington"
+ },
+ "country": {
+ "multiple": false,
+ "typeClass": "controlledVocabulary",
+ "typeName": "country",
+ "value": "United States"
+ },
+ "state": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "state",
+ "value": "MA"
+ }
+ },
+ {
+ "city": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "city",
+ "value": "beachcity"
+ },
+ "country": {
+ "multiple": false,
+ "typeClass": "controlledVocabulary",
+ "typeName": "country",
+ "value": "Aruba"
+ },
+ "state": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "state",
+ "value": "beach"
+ }
+ }
+ ]
+ },
+ {
+ "multiple": false,
+ "typeClass": "compound",
+ "typeName": "geographicBoundingBox",
+ "value":
+ {
+ "eastLongitude": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "eastLongitude",
+ "value": "23"
+ },
+ "northLatitude": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "northLatitude",
+ "value": "786"
+ },
+ "southLatitude": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "southLatitude",
+ "value": "34"
+ },
+ "westLongitude": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "westLongitude",
+ "value": "45"
+ }
+ }
+
+ }
+ ]
+ },
+ "socialscience": {
+ "displayName": "Social Science and Humanities Metadata",
+ "fields": [
+ {
+ "multiple": true,
+ "typeClass": "compound",
+ "typeName": "software",
+ "value": [
+ {
+ "softwareName": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "softwareName",
+ "value": "softwareName"
+ },
+ "softwareVersion": {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "softwareVersion",
+ "value": "software version"
+ }
+ }
+ ]
+ },
+ {
+ "multiple": false,
+ "typeClass": "primitive",
+ "typeName": "unitOfAnalysis",
+ "value": "unit of analysis"
+ }
+ ]
+ }
+ },
+ "productionDate": "Production Date",
+ "versionState": "DRAFT"
+ }
From 86b226008e3ba53b0a0aefb0fd0fe9b15087c3f3 Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Sun, 24 Nov 2024 21:38:03 -0500
Subject: [PATCH 200/270] tests for selecting persistent ids in the
GenericImportService (from PR #11010 by @stevenferey)
---
.../api/imports/ImportGenericServiceBean.java | 4 ++
.../imports/ImportGenericServiceBeanTest.java | 53 ++++++++++++++++++-
2 files changed, 56 insertions(+), 1 deletion(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportGenericServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportGenericServiceBean.java
index 7bce0947a0e..aa5b25e3967 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportGenericServiceBean.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/imports/ImportGenericServiceBean.java
@@ -367,6 +367,10 @@ private FieldDTO makeDTO(DatasetFieldType dataverseFieldType, FieldDTO value, St
return value;
}
+ public String selectIdentifier(DatasetVersionDTO datasetVersionDTO, String suppliedIdentifier) {
+ return selectIdentifier(datasetVersionDTO, suppliedIdentifier, false);
+ }
+
private String selectIdentifier(DatasetVersionDTO datasetVersionDTO, String suppliedIdentifier, boolean preferSuppliedIdentifier) {
List otherIds = new ArrayList<>();
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/imports/ImportGenericServiceBeanTest.java b/src/test/java/edu/harvard/iq/dataverse/api/imports/ImportGenericServiceBeanTest.java
index 44739f3f62a..acf5d970358 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/imports/ImportGenericServiceBeanTest.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/imports/ImportGenericServiceBeanTest.java
@@ -1,6 +1,13 @@
package edu.harvard.iq.dataverse.api.imports;
import edu.harvard.iq.dataverse.api.dto.DatasetDTO;
+import edu.harvard.iq.dataverse.api.dto.DatasetVersionDTO;
+
+import org.apache.commons.io.FileUtils;
+import com.google.gson.Gson;
+import java.io.File;
+import java.io.IOException;
+
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
@@ -8,6 +15,8 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
+import java.nio.charset.StandardCharsets;
+
@ExtendWith(MockitoExtension.class)
public class ImportGenericServiceBeanTest {
@@ -15,7 +24,47 @@ public class ImportGenericServiceBeanTest {
private ImportGenericServiceBean importGenericService;
@Test
- public void testReassignIdentifierAsGlobalId() {
+ void testIdentifierHarvestableWithOtherID() throws IOException {
+ // "otherIdValue" containing the value : doi:10.7910/DVN/TJCLKP
+ File file = new File("src/test/resources/json/importGenericWithOtherId.json");
+ String text = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
+ DatasetVersionDTO dto = new Gson().fromJson(text, DatasetVersionDTO.class);
+
+ assertEquals("doi:10.7910/DVN/TJCLKP", importGenericService.selectIdentifier(dto, "https://doi.org/10.7910/DVN/TJCLKP"));
+ // junk or null
+ assertEquals("doi:10.7910/DVN/TJCLKP", importGenericService.selectIdentifier(dto, "junk"));
+ assertEquals("doi:10.7910/DVN/TJCLKP", importGenericService.selectIdentifier(dto, null));
+ assertEquals("doi:10.7910/DVN/TJCLKP", importGenericService.selectIdentifier(dto, "http://www.example.com"));
+ assertEquals("doi:10.7910/DVN/TJCLKP", importGenericService.selectIdentifier(dto, "https://dataverse.org"));
+ }
+
+ @Test
+ void testIdentifierHarvestableWithoutOtherID() throws IOException {
+ // Does not contain data of type "otherIdValue"
+ File file = new File("src/test/resources/json/importGenericWithoutOtherId.json");
+ String text = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
+ DatasetVersionDTO dto = new Gson().fromJson(text, DatasetVersionDTO.class);
+
+ // non-URL
+ assertEquals("doi:10.7910/DVN/TJCLKP", importGenericService.selectIdentifier(dto, "doi:10.7910/DVN/TJCLKP"));
+ assertEquals("hdl:10.7910/DVN/TJCLKP", importGenericService.selectIdentifier(dto, "hdl:10.7910/DVN/TJCLKP"));
+ // HTTPS
+ assertEquals("https://doi.org/10.7910/DVN/TJCLKP", importGenericService.selectIdentifier(dto, "https://doi.org/10.7910/DVN/TJCLKP"));
+ assertEquals("https://dx.doi.org/10.7910/DVN/TJCLKP", importGenericService.selectIdentifier(dto, "https://dx.doi.org/10.7910/DVN/TJCLKP"));
+ assertEquals("https://hdl.handle.net/10.7910/DVN/TJCLKP", importGenericService.selectIdentifier(dto, "https://hdl.handle.net/10.7910/DVN/TJCLKP"));
+ // HTTP (no S)
+ assertEquals("http://doi.org/10.7910/DVN/TJCLKP", importGenericService.selectIdentifier(dto, "http://doi.org/10.7910/DVN/TJCLKP"));
+ assertEquals("http://dx.doi.org/10.7910/DVN/TJCLKP", importGenericService.selectIdentifier(dto, "http://dx.doi.org/10.7910/DVN/TJCLKP"));
+ assertEquals("http://hdl.handle.net/10.7910/DVN/TJCLKP", importGenericService.selectIdentifier(dto, "http://hdl.handle.net/10.7910/DVN/TJCLKP"));
+ // junk or null
+ assertNull(importGenericService.selectIdentifier(dto, "junk"));
+ assertNull(importGenericService.selectIdentifier(dto, null));
+ assertNull(importGenericService.selectIdentifier(dto, "http://www.example.com"));
+ assertNull(importGenericService.selectIdentifier(dto, "https://dataverse.org"));
+ }
+
+ @Test
+ void testReassignIdentifierAsGlobalId() {
// non-URL
assertEquals("doi:10.7910/DVN/TJCLKP", importGenericService.reassignIdentifierAsGlobalId("doi:10.7910/DVN/TJCLKP", new DatasetDTO()));
assertEquals("hdl:10.7910/DVN/TJCLKP", importGenericService.reassignIdentifierAsGlobalId("hdl:10.7910/DVN/TJCLKP", new DatasetDTO()));
@@ -29,6 +78,8 @@ public void testReassignIdentifierAsGlobalId() {
assertEquals("hdl:10.7910/DVN/TJCLKP", importGenericService.reassignIdentifierAsGlobalId("http://hdl.handle.net/10.7910/DVN/TJCLKP", new DatasetDTO()));
// junk
assertNull(importGenericService.reassignIdentifierAsGlobalId("junk", new DatasetDTO()));
+ assertNull(importGenericService.reassignIdentifierAsGlobalId("http://www.example.com", new DatasetDTO()));
+ assertNull(importGenericService.reassignIdentifierAsGlobalId("https://dataverse.org", new DatasetDTO()));
}
}
From 115c88eb37d6288d9af1d9a8a2abacd230407700 Mon Sep 17 00:00:00 2001
From: landreev
Date: Mon, 25 Nov 2024 11:07:47 -0500
Subject: [PATCH 201/270] Update
doc/release-notes/11049-oai-identifiers-as-pids.md
Co-authored-by: Philip Durbin
---
doc/release-notes/11049-oai-identifiers-as-pids.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/release-notes/11049-oai-identifiers-as-pids.md b/doc/release-notes/11049-oai-identifiers-as-pids.md
index 2f857bf2198..8b53a461a70 100644
--- a/doc/release-notes/11049-oai-identifiers-as-pids.md
+++ b/doc/release-notes/11049-oai-identifiers-as-pids.md
@@ -2,4 +2,4 @@
This will allow harvesting from sources that do not include a persistent id in their oai_dc metadata records, but use valid dois or handles as the OAI-PMH record header identifiers.
-It is also possible to optionally configure a harvesting client to use this OAI-PMH identifier as the **preferred** choice for the persistent id. See the [Harvesting Clients API](https://guides.dataverse.org/en/6.5/api/native-api.html#create-a-harvesting-client) section of the Guides for more information.
\ No newline at end of file
+It is also possible to optionally configure a harvesting client to use this OAI-PMH identifier as the **preferred** choice for the persistent id. See the [Harvesting Clients API](https://guides.dataverse.org/en/6.5/api/native-api.html#create-a-harvesting-client) section of the Guides, #11049 and #10982 for more information.
\ No newline at end of file
From a295cc4a34b4836b087fd662e932838ddb278a87 Mon Sep 17 00:00:00 2001
From: landreev
Date: Mon, 25 Nov 2024 11:08:07 -0500
Subject: [PATCH 202/270] 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 1f36691be0d..641e443e54a 100644
--- a/doc/sphinx-guides/source/api/native-api.rst
+++ b/doc/sphinx-guides/source/api/native-api.rst
@@ -5281,7 +5281,7 @@ The following optional fields are supported:
- style: Defaults to "default" - a generic OAI archive. (Make sure to use "dataverse" when configuring harvesting from another Dataverse installation).
- customHeaders: This can be used to configure this client with a specific HTTP header that will be added to every OAI request. This is to accommodate a use case where the remote server requires this header to supply some form of a token in order to offer some content not available to other clients. See the example below. Multiple headers can be supplied separated by `\\n` - actual "backslash" and "n" characters, not a single "new line" character.
- allowHarvestingMissingCVV: Flag to allow datasets to be harvested with Controlled Vocabulary Values that existed in the originating Dataverse Project but are not in the harvesting Dataverse Project. (Default is false). Currently only settable using API.
-- useOaiIdentifiersAsPids: Defaults to false; if set to true, Harvester will attempt to use the identifier from the OAI-PMH record header as the **first choice** for the persistent id of the harvested dataset. When set to false, Dataverse will still attempt to use this identifier, but only if none of the `` entries in the OAI_DC record contain a valid persistent id (this is new as of v6.5).
+- useOaiIdentifiersAsPids: Defaults to false; if set to true, the harvester will attempt to use the identifier from the OAI-PMH record header as the **first choice** for the persistent id of the harvested dataset. When set to false, Dataverse will still attempt to use this identifier, but only if none of the `` entries in the OAI_DC record contain a valid persistent id (this is new as of v6.5).
Generally, the API will accept the output of the GET version of the API for an existing client as valid input, but some fields will be ignored. For example, as of writing this there is no way to configure a harvesting schedule via this API.
From 3c4628786f18c235707afc6a282969da11ed4c9d Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Mon, 25 Nov 2024 11:22:01 -0500
Subject: [PATCH 203/270] reverted the flyway script back to its original state
(a newline was added when resolving a conflict with a cherry-picked commit,
which of course changes the checksum)
---
src/main/resources/db/migration/V6.4.0.1.sql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/resources/db/migration/V6.4.0.1.sql b/src/main/resources/db/migration/V6.4.0.1.sql
index 438c52a192e..0bcd87dd736 100644
--- a/src/main/resources/db/migration/V6.4.0.1.sql
+++ b/src/main/resources/db/migration/V6.4.0.1.sql
@@ -1,4 +1,4 @@
-- Adding a case-insensitive index related to #11003
--
-CREATE UNIQUE INDEX IF NOT EXISTS INDEX_DVOBJECT_authority_protocol_upper_identifier ON dvobject (authority, protocol, UPPER(identifier));
+CREATE UNIQUE INDEX IF NOT EXISTS INDEX_DVOBJECT_authority_protocol_upper_identifier ON dvobject (authority, protocol, UPPER(identifier));
\ No newline at end of file
From 8a361be347e639aeedc68b6f9bc93c18e2f6eaaf Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Mon, 25 Nov 2024 11:26:37 -0500
Subject: [PATCH 204/270] another cherry-picked commit not needed in this
branch.
---
.../harvard/iq/dataverse/harvest/client/HarvestingClient.java | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/harvest/client/HarvestingClient.java b/src/main/java/edu/harvard/iq/dataverse/harvest/client/HarvestingClient.java
index de9cc7c0db6..7280b6af129 100644
--- a/src/main/java/edu/harvard/iq/dataverse/harvest/client/HarvestingClient.java
+++ b/src/main/java/edu/harvard/iq/dataverse/harvest/client/HarvestingClient.java
@@ -214,7 +214,6 @@ public void setArchiveDescription(String archiveDescription) {
this.archiveDescription = archiveDescription;
}
- @Column(columnDefinition="TEXT")
private String harvestingSet;
public String getHarvestingSet() {
From f0e19168ebedf708e3e0e0df1876b57ca378af88 Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Mon, 25 Nov 2024 15:33:28 -0500
Subject: [PATCH 205/270] #11044 refresh facet array
---
.../iq/dataverse/DataverseFacetServiceBean.java | 14 ++++++++++----
.../impl/AbstractWriteDataverseCommand.java | 3 ++-
2 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/DataverseFacetServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/DataverseFacetServiceBean.java
index 5c77989f6d6..67dc183ba66 100644
--- a/src/main/java/edu/harvard/iq/dataverse/DataverseFacetServiceBean.java
+++ b/src/main/java/edu/harvard/iq/dataverse/DataverseFacetServiceBean.java
@@ -4,6 +4,8 @@
import java.util.List;
import jakarta.ejb.EJB;
import jakarta.ejb.Stateless;
+import jakarta.ejb.TransactionAttribute;
+import jakarta.ejb.TransactionAttributeType;
import jakarta.inject.Named;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
@@ -42,23 +44,27 @@ public void delete(DataverseFacet dataverseFacet) {
cache.invalidate();
}
+ @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void deleteFacetsFor( Dataverse d ) {
em.createNamedQuery("DataverseFacet.removeByOwnerId")
.setParameter("ownerId", d.getId())
.executeUpdate();
cache.invalidate(d.getId());
-
+
}
- public DataverseFacet create(int displayOrder, DatasetFieldType fieldType, Dataverse ownerDv) {
+ @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
+ public DataverseFacet create(int displayOrder, DatasetFieldType fieldType, Dataverse ownerDv) {
DataverseFacet dataverseFacet = new DataverseFacet();
dataverseFacet.setDisplayOrder(displayOrder);
dataverseFacet.setDatasetFieldType(fieldType);
dataverseFacet.setDataverse(ownerDv);
-
- ownerDv.getDataverseFacets().add(dataverseFacet);
+
em.persist(dataverseFacet);
+ ownerDv.getDataverseFacets().add(dataverseFacet);
+ em.merge(ownerDv);
+ cache.invalidate(ownerDv.getId());
return dataverseFacet;
}
diff --git a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractWriteDataverseCommand.java b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractWriteDataverseCommand.java
index 40c2abf5d21..ede07ba5ab7 100644
--- a/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractWriteDataverseCommand.java
+++ b/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/AbstractWriteDataverseCommand.java
@@ -56,7 +56,8 @@ public Dataverse execute(CommandContext ctxt) throws CommandException {
if (facets != null) {
ctxt.facets().deleteFacetsFor(dataverse);
-
+ dataverse.setDataverseFacets(new ArrayList<>());
+
if (!facets.isEmpty()) {
dataverse.setFacetRoot(true);
}
From e8093c62089460f8cadd6c8b9fb9e6da1530c60f Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Mon, 25 Nov 2024 17:30:30 -0500
Subject: [PATCH 206/270] Per review feedback, made it impossible to supply the
file sizes via the /addFiles API (i.e., we don't want to trust the users of
the direct s3 upload api when it comes to file sizes). #10977
---
.../datasetutility/AddReplaceFileHelper.java | 48 ++++++++++++-------
.../dataverse/globus/GlobusServiceBean.java | 2 +-
2 files changed, 33 insertions(+), 17 deletions(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/datasetutility/AddReplaceFileHelper.java b/src/main/java/edu/harvard/iq/dataverse/datasetutility/AddReplaceFileHelper.java
index 3943e3ad7d8..6b98848021c 100644
--- a/src/main/java/edu/harvard/iq/dataverse/datasetutility/AddReplaceFileHelper.java
+++ b/src/main/java/edu/harvard/iq/dataverse/datasetutility/AddReplaceFileHelper.java
@@ -136,10 +136,7 @@ public class AddReplaceFileHelper{
private String newFileName; // step 30
private String newFileContentType; // step 30
private String newStorageIdentifier; // step 30
- private String newCheckSum; // step 30
- private ChecksumType newCheckSumType; //step 30
- private Long suppliedFileSize = null;
-
+
// -- Optional
private DataFile fileToReplace; // step 25
@@ -147,6 +144,7 @@ public class AddReplaceFileHelper{
private DatasetVersion clone;
List initialFileList;
List finalFileList;
+ private boolean trustSuppliedFileSizes;
// -----------------------------------
// Ingested files
@@ -611,18 +609,9 @@ private boolean runAddReplacePhase1(Dataset owner,
return false;
}
- if (optionalFileParams != null) {
- if (optionalFileParams.hasCheckSum()) {
- newCheckSum = optionalFileParams.getCheckSum();
- newCheckSumType = optionalFileParams.getCheckSumType();
- }
- if (optionalFileParams.hasFileSize()) {
- suppliedFileSize = optionalFileParams.getFileSize();
- }
- }
msgt("step_030_createNewFilesViaIngest");
- if (!this.step_030_createNewFilesViaIngest()){
+ if (!this.step_030_createNewFilesViaIngest(optionalFileParams)){
return false;
}
@@ -1195,7 +1184,7 @@ private boolean step_007_auto_isReplacementInLatestVersion(DataFile existingFile
}
- private boolean step_030_createNewFilesViaIngest(){
+ private boolean step_030_createNewFilesViaIngest(OptionalFileParams optionalFileParams){
if (this.hasError()){
return false;
@@ -1207,6 +1196,22 @@ private boolean step_030_createNewFilesViaIngest(){
//Don't repeatedly update the clone (losing changes) in multifile case
clone = workingVersion.cloneDatasetVersion();
}
+
+ Long suppliedFileSize = null;
+ String newCheckSum = null;
+ ChecksumType newCheckSumType = null;
+
+
+ if (optionalFileParams != null) {
+ if (optionalFileParams.hasCheckSum()) {
+ newCheckSum = optionalFileParams.getCheckSum();
+ newCheckSumType = optionalFileParams.getCheckSumType();
+ }
+ if (trustSuppliedFileSizes && optionalFileParams.hasFileSize()) {
+ suppliedFileSize = optionalFileParams.getFileSize();
+ }
+ }
+
try {
UploadSessionQuotaLimit quota = null;
if (systemConfig.isStorageQuotasEnforced()) {
@@ -2028,9 +2033,15 @@ public void setDuplicateFileWarning(String duplicateFileWarning) {
* @param jsonData - an array of jsonData entries (one per file) using the single add file jsonData format
* @param dataset
* @param authUser
+ * @param trustSuppliedSizes - whether to accept the fileSize values passed
+ * in jsonData (we don't want to trust the users of the S3 direct
+ * upload API with that information - we will verify the status of
+ * the files in the S3 bucket and confirm the sizes in the process.
+ * we do want GlobusService to be able to pass the file sizes, since
+ * they are obtained and verified via a Globus API lookup).
* @return
*/
- public Response addFiles(String jsonData, Dataset dataset, User authUser) {
+ public Response addFiles(String jsonData, Dataset dataset, User authUser, boolean trustSuppliedFileSizes) {
msgt("(addFilesToDataset) jsonData: " + jsonData.toString());
JsonArrayBuilder jarr = Json.createArrayBuilder();
@@ -2039,6 +2050,7 @@ public Response addFiles(String jsonData, Dataset dataset, User authUser) {
int totalNumberofFiles = 0;
int successNumberofFiles = 0;
+ this.trustSuppliedFileSizes = trustSuppliedFileSizes;
// -----------------------------------------------------------
// Read jsonData and Parse files information from jsondata :
// -----------------------------------------------------------
@@ -2171,6 +2183,10 @@ public Response addFiles(String jsonData, Dataset dataset, User authUser) {
.add("data", Json.createObjectBuilder().add("Files", jarr).add("Result", result)).build() ).build();
}
+ public Response addFiles(String jsonData, Dataset dataset, User authUser) {
+ return addFiles(jsonData, dataset, authUser, false);
+ }
+
/**
* Replace multiple files with prepositioned replacements as listed in the
* jsonData. Works with direct upload, Globus, and other out-of-band methods.
diff --git a/src/main/java/edu/harvard/iq/dataverse/globus/GlobusServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/globus/GlobusServiceBean.java
index 5c9a2f1d946..58992805dc8 100644
--- a/src/main/java/edu/harvard/iq/dataverse/globus/GlobusServiceBean.java
+++ b/src/main/java/edu/harvard/iq/dataverse/globus/GlobusServiceBean.java
@@ -1093,7 +1093,7 @@ private void processUploadedFiles(JsonArray filesJsonArray, Dataset dataset, Aut
// The old code had 2 sec. of sleep, so ...
Thread.sleep(2000);
- Response addFilesResponse = addFileHelper.addFiles(newjsonData, dataset, authUser);
+ Response addFilesResponse = addFileHelper.addFiles(newjsonData, dataset, authUser, true);
if (addFilesResponse == null) {
logger.info("null response from addFiles call");
From 3cd9a82d381f543cd3cb9b3a5560cb44bee1bbee Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Mon, 25 Nov 2024 17:39:46 -0500
Subject: [PATCH 207/270] We do support 0-size files! #10977
---
.../java/edu/harvard/iq/dataverse/ingest/IngestServiceBean.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/ingest/IngestServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/ingest/IngestServiceBean.java
index fad02c76c78..52b7d4f1861 100644
--- a/src/main/java/edu/harvard/iq/dataverse/ingest/IngestServiceBean.java
+++ b/src/main/java/edu/harvard/iq/dataverse/ingest/IngestServiceBean.java
@@ -350,7 +350,7 @@ public List saveAndAddFilesToDataset(DatasetVersion version,
// to S3 that go through the jsf dataset page. Or the Globus
// uploads, where the file sizes are looked up in bulk on
// the completion of the remote upload task.
- if (dataFile.getFilesize() > 0) {
+ if (dataFile.getFilesize() >= 0) {
confirmedFileSize = dataFile.getFilesize();
} else {
dataAccess.open(DataAccessOption.READ_ACCESS);
From 644a52491665d4e0f43a655f6b9094a2c41848b0 Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Mon, 25 Nov 2024 18:33:37 -0500
Subject: [PATCH 208/270] added a bunch of globus-related entries that were
missing from the bundle, per #11030 (these are used by the notification page,
apparently??)
---
src/main/java/propertyFiles/Bundle.properties | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties
index 012b389ce32..2b74e24ea29 100644
--- a/src/main/java/propertyFiles/Bundle.properties
+++ b/src/main/java/propertyFiles/Bundle.properties
@@ -307,7 +307,13 @@ notification.typeDescription.WORKFLOW_FAILURE=External workflow run has failed
notification.typeDescription.STATUSUPDATED=Status of dataset has been updated
notification.typeDescription.DATASETCREATED=Dataset was created by user
notification.typeDescription.DATASETMENTIONED=Dataset was referenced in remote system
-
+notification.typeDescription.GLOBUSUPLOADCOMPLETED=Globus upload is completed
+notification.typeDescription.GLOBUSUPLOADCOMPLETEDWITHERRORS=Globus upload completed with errors
+notification.typeDescription.GLOBUSDOWNLOADCOMPLETED=Globus download is completed
+notification.typeDescription.GLOBUSDOWNLOADCOMPLETEDWITHERRORS=Globus download completed with errors
+notification.typeDescription.GLOBUSUPLOADLOCALFAILURE=Globus upload failed, internal error
+notification.typeDescription.GLOBUSUPLOADREMOTEFAILURE=Globus upload failed, remote transfer error
+notification.typeDescription.REQUESTEDFILEACCESS=File access requested
groupAndRoles.manageTips=Here is where you can access and manage all the groups you belong to, and the roles you have been assigned.
user.message.signup.label=Create Account
user.message.signup.tip=Why have a Dataverse account? To create your own dataverse and customize it, add datasets, or request access to restricted files.
From 28e25eae93de311a2eb4e5e4971d5463fafb9193 Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Mon, 25 Nov 2024 18:52:35 -0500
Subject: [PATCH 209/270] one more missing notification entry in the bundle
#10977
---
src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java | 2 +-
src/main/java/propertyFiles/Bundle.properties | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java
index 2995c0c5f47..c67a0293847 100644
--- a/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java
+++ b/src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java
@@ -283,7 +283,7 @@ public Boolean sendNotificationEmail(UserNotification notification, String comme
if (objectOfNotification != null){
String messageText = getMessageTextBasedOnNotification(notification, objectOfNotification, comment, requestor);
String subjectText = MailUtil.getSubjectTextBasedOnNotification(notification, objectOfNotification);
- if (!(messageText.isEmpty() || subjectText.isEmpty())){
+ if (!(StringUtils.isEmpty(messageText) || StringUtils.isEmpty(subjectText))){
retval = sendSystemEmail(emailAddress, subjectText, messageText, isHtmlContent);
} else {
logger.warning("Skipping " + notification.getType() + " notification, because couldn't get valid message");
diff --git a/src/main/java/propertyFiles/Bundle.properties b/src/main/java/propertyFiles/Bundle.properties
index 2b74e24ea29..02bc19f86cf 100644
--- a/src/main/java/propertyFiles/Bundle.properties
+++ b/src/main/java/propertyFiles/Bundle.properties
@@ -843,7 +843,8 @@ notification.email.datasetWasMentioned=Hello {0},
The {1} has just been
notification.email.datasetWasMentioned.subject={0}: A Dataset Relationship has been reported!
notification.email.globus.uploadCompleted.subject={0}: Files uploaded successfully via Globus and verified
notification.email.globus.downloadCompleted.subject={0}: Files downloaded successfully via Globus
-notification.email.globus.uploadCompletedWithErrors.subject={0}: Uploaded files via Globus with errors
+notification.email.globus.downloadCompletedWithErrors.subject={0}: Globus download task completed, errors encountered
+notification.email.globus.uploadCompletedWithErrors.subject={0}: Globus upload task completed with errors
notification.email.globus.uploadFailedRemotely.subject={0}: Failed to upload files via Globus
notification.email.globus.uploadFailedLocally.subject={0}: Failed to add files uploaded via Globus to dataset
# dataverse.xhtml
From 1325cee6cc7aa707481db68590fa16b65a7ad2ef Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Tue, 26 Nov 2024 09:47:26 -0500
Subject: [PATCH 210/270] This should make the filesize setting logic less
confusing potentially #10977
---
.../java/edu/harvard/iq/dataverse/ingest/IngestServiceBean.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/edu/harvard/iq/dataverse/ingest/IngestServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/ingest/IngestServiceBean.java
index 52b7d4f1861..71c498a4d0b 100644
--- a/src/main/java/edu/harvard/iq/dataverse/ingest/IngestServiceBean.java
+++ b/src/main/java/edu/harvard/iq/dataverse/ingest/IngestServiceBean.java
@@ -372,7 +372,7 @@ public List saveAndAddFilesToDataset(DatasetVersion version,
if (fileSizeLimit == null || confirmedFileSize < fileSizeLimit) {
//set file size
- if (dataFile.getFilesize() < 1) {
+ if (dataFile.getFilesize() < 0) {
logger.fine("Setting file size: " + confirmedFileSize);
dataFile.setFilesize(confirmedFileSize);
}
From 321de7c46b34a53bc9df64b9947fab47f9f126a6 Mon Sep 17 00:00:00 2001
From: Leonid Andreev
Date: Tue, 26 Nov 2024 10:35:59 -0500
Subject: [PATCH 211/270] there's no need to slap the "incomplete metadata"
label on harvested dataset cards #10909
---
src/main/webapp/search-include-fragment.xhtml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/webapp/search-include-fragment.xhtml b/src/main/webapp/search-include-fragment.xhtml
index 505fe681363..fcc5aff6f92 100644
--- a/src/main/webapp/search-include-fragment.xhtml
+++ b/src/main/webapp/search-include-fragment.xhtml
@@ -582,7 +582,7 @@
-
+
@@ -1252,7 +1252,7 @@
#{bundle['copyClipboard']}
-
+
From 452cca4f15805063b769003620762a44cdc8260b Mon Sep 17 00:00:00 2001
From: Philip Durbin
Date: Tue, 10 Dec 2024 14:49:00 -0500
Subject: [PATCH 262/270] word choice
Co-authored-by: Omer Fahim
---
doc/release-notes/6.5-release-notes.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/release-notes/6.5-release-notes.md b/doc/release-notes/6.5-release-notes.md
index 7bb9cbb1e73..a2cac8ed800 100644
--- a/doc/release-notes/6.5-release-notes.md
+++ b/doc/release-notes/6.5-release-notes.md
@@ -306,7 +306,7 @@ sudo service solr stop
Replace schema.xml
-Please note that the path to Solr may differ than the example below.
+Please note that the path to Solr may differ from the example below.
```shell
wget https://raw.githubusercontent.com/IQSS/dataverse/v6.5/conf/solr/schema.xml
From 4c803d2cd14236bc98f925cce4ed2a348eb4e3ee Mon Sep 17 00:00:00 2001
From: qqmyers
Date: Wed, 11 Dec 2024 11:13:54 -0500
Subject: [PATCH 263/270] keep status checks
---
src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java
index ffb7aa4cc3b..98107eca33a 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java
@@ -2274,6 +2274,8 @@ public void testDeleteFile() {
// Check file 2 still in v1.0
Response v1 = UtilIT.getDatasetVersion(datasetPid, "1.0", apiToken);
v1.prettyPrint();
+ v1.then().assertThat()
+ .statusCode(OK.getStatusCode());
Map v1files1 = with(v1.body().asString()).param("fileToFind", "cc0.png")
.getJsonObject("data.files.find { files -> files.label == fileToFind }");
@@ -2286,6 +2288,8 @@ public void testDeleteFile() {
// Check file 3 still in post v1.0 draft
Response postv1draft2 = UtilIT.getDatasetVersion(datasetPid, DS_VERSION_DRAFT, apiToken);
postv1draft2.prettyPrint();
+ postv1draft2.then().assertThat()
+ .statusCode(OK.getStatusCode());
Map v1files2 = with(postv1draft2.body().asString()).param("fileToFind", "orcid_16x16.png")
.getJsonObject("data.files.find { files -> files.label == fileToFind }");
From ce6a99a49f54caaf54bce633dd08869365c87e8c Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Wed, 11 Dec 2024 13:38:31 -0500
Subject: [PATCH 264/270] #10952 add not about breadcrumbs in Anon Preview URL
---
doc/release-notes/6.5-release-notes.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/doc/release-notes/6.5-release-notes.md b/doc/release-notes/6.5-release-notes.md
index a2cac8ed800..c866d52e42c 100644
--- a/doc/release-notes/6.5-release-notes.md
+++ b/doc/release-notes/6.5-release-notes.md
@@ -24,6 +24,8 @@ The name of the URL that may be used by dataset administrators to share a draft
Also, additional information about the creation of Preview URLs has been added to the popup accessed via edit menu of the Dataset Page.
+Users of the Anonymous Preview URL will no longer be able to see the name of the Dataverse that this dataset is in but will be able to see the name of the repository.
+
Any Private URLs created in previous versions of Dataverse will continue to work.
The old "privateUrl" API endpoints for the creation and deletion of Preview (formerly Private) URLs have been deprecated. They will continue to work but please switch to the "previewUrl" equivalents that have been [documented](https://guides.dataverse.org/en/6.5/api/native-api.html#create-a-preview-url-for-a-dataset) in the API Guide.
From 929128bb344df97678c49e3efa31d22c5074e646 Mon Sep 17 00:00:00 2001
From: Stephen Kraffmiller
Date: Wed, 11 Dec 2024 13:40:19 -0500
Subject: [PATCH 265/270] #10952 reword
---
doc/release-notes/6.5-release-notes.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/release-notes/6.5-release-notes.md b/doc/release-notes/6.5-release-notes.md
index c866d52e42c..17abec2907a 100644
--- a/doc/release-notes/6.5-release-notes.md
+++ b/doc/release-notes/6.5-release-notes.md
@@ -24,7 +24,7 @@ The name of the URL that may be used by dataset administrators to share a draft
Also, additional information about the creation of Preview URLs has been added to the popup accessed via edit menu of the Dataset Page.
-Users of the Anonymous Preview URL will no longer be able to see the name of the Dataverse that this dataset is in but will be able to see the name of the repository.
+Users of the Anonymous Preview URL will no longer be able to see the name of the Dataverse that the dataset is in but will be able to see the name of the repository.
Any Private URLs created in previous versions of Dataverse will continue to work.
From a0508d1989c94e87e7fd7ddfaf7853cc69936224 Mon Sep 17 00:00:00 2001
From: Philip Durbin
Date: Wed, 11 Dec 2024 13:59:56 -0500
Subject: [PATCH 266/270] add link to #11085
---
doc/release-notes/6.5-release-notes.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/release-notes/6.5-release-notes.md b/doc/release-notes/6.5-release-notes.md
index 17abec2907a..a45efb59556 100644
--- a/doc/release-notes/6.5-release-notes.md
+++ b/doc/release-notes/6.5-release-notes.md
@@ -30,7 +30,7 @@ Any Private URLs created in previous versions of Dataverse will continue to work
The old "privateUrl" API endpoints for the creation and deletion of Preview (formerly Private) URLs have been deprecated. They will continue to work but please switch to the "previewUrl" equivalents that have been [documented](https://guides.dataverse.org/en/6.5/api/native-api.html#create-a-preview-url-for-a-dataset) in the API Guide.
-See also #8184, #8185, #10950, and #10961.
+See also #8184, #8185, #10950, #10961, and #11085.
### Showing Differences Between Dataset Versions is More Scalable
From fe13a437c9c990086945c48a21dd32ce7e80ee6f Mon Sep 17 00:00:00 2001
From: Philip Durbin
Date: Wed, 11 Dec 2024 15:13:43 -0500
Subject: [PATCH 267/270] add cvoc overview fix #10952
---
doc/release-notes/6.5-release-notes.md | 4 ++++
doc/release-notes/display_overview_fix.md | 1 -
2 files changed, 4 insertions(+), 1 deletion(-)
delete mode 100644 doc/release-notes/display_overview_fix.md
diff --git a/doc/release-notes/6.5-release-notes.md b/doc/release-notes/6.5-release-notes.md
index a45efb59556..8cccc7114cf 100644
--- a/doc/release-notes/6.5-release-notes.md
+++ b/doc/release-notes/6.5-release-notes.md
@@ -134,6 +134,10 @@ A minor bug fix was made to avoid sending a useless ", null" in the DataCiteXML
Make Data Count (MDC) citation retrieval with the PID settings has been fixed. PID parsing in Dataverse is now case insensitive, improving interaction with services that may change the case of PIDs. Warnings related to managed/excluded PID lists for PID providers have been reduced. See #10708.
+### Quirk in Overview Display When Using External Controlled Variables
+
+This bugfix corrects an issue when there are duplicated entries on the metadata page. It is fixed by correcting an IF-clause in metadataFragment.xhtml. See #11005 and #11034.
+
### Globus "missing properties" Logging Fixed
In previous releases, logging would show Globus-related strings were missing from properties files. This has been fixed. See #11030.
diff --git a/doc/release-notes/display_overview_fix.md b/doc/release-notes/display_overview_fix.md
deleted file mode 100644
index 73a01435caf..00000000000
--- a/doc/release-notes/display_overview_fix.md
+++ /dev/null
@@ -1 +0,0 @@
-This bugfix corrects an issue when there are duplicated entries on the metadata page. It is fixed by correcting an IF-clause in metadataFragment.xhtml.
\ No newline at end of file
From 2de57e13135a8512218a651552e924cce512382a Mon Sep 17 00:00:00 2001
From: Philip Durbin
Date: Wed, 11 Dec 2024 15:16:26 -0500
Subject: [PATCH 268/270] remove "in place" from Solr reindex step #10952
---
doc/release-notes/6.5-release-notes.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/release-notes/6.5-release-notes.md b/doc/release-notes/6.5-release-notes.md
index 8cccc7114cf..2e27f4419bd 100644
--- a/doc/release-notes/6.5-release-notes.md
+++ b/doc/release-notes/6.5-release-notes.md
@@ -354,7 +354,7 @@ Below is the simplest way to reindex Solr:
curl http://localhost:8080/api/admin/index
```
-The API above rebuilds the existing index "in place". If you want to be absolutely sure that your index is up-to-date and consistent, you may consider wiping it clean and reindexing everything from scratch (see [the guides](https://guides.dataverse.org/en/latest/admin/solr-search-index.html)). Just note that, depending on the size of your database, a full reindex may take a while and the users will be seeing incomplete search results during that window.
+The API above rebuilds the existing index. If you want to be absolutely sure that your index is up-to-date and consistent, you may consider wiping it clean and reindexing everything from scratch (see [the guides](https://guides.dataverse.org/en/latest/admin/solr-search-index.html)). Just note that, depending on the size of your database, a full reindex may take a while and the users will be seeing incomplete search results during that window.
9\. Run reExportAll to update dataset metadata exports
From 2c08be4a3550fdd7487969e12dbbf88cbe1712ea Mon Sep 17 00:00:00 2001
From: Philip Durbin
Date: Wed, 11 Dec 2024 16:12:00 -0500
Subject: [PATCH 269/270] bump version to 6.5 #10954
---
doc/sphinx-guides/source/conf.py | 4 ++--
doc/sphinx-guides/source/versions.rst | 3 ++-
modules/dataverse-parent/pom.xml | 2 +-
3 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/doc/sphinx-guides/source/conf.py b/doc/sphinx-guides/source/conf.py
index 7ee355302d8..fc88de1fcd7 100755
--- a/doc/sphinx-guides/source/conf.py
+++ b/doc/sphinx-guides/source/conf.py
@@ -68,9 +68,9 @@
# built documents.
#
# The short X.Y version.
-version = '6.4'
+version = '6.5'
# The full version, including alpha/beta/rc tags.
-release = '6.4'
+release = '6.5'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/doc/sphinx-guides/source/versions.rst b/doc/sphinx-guides/source/versions.rst
index 800bdc6e0f9..9d640bd22bd 100755
--- a/doc/sphinx-guides/source/versions.rst
+++ b/doc/sphinx-guides/source/versions.rst
@@ -7,7 +7,8 @@ Dataverse Software Documentation Versions
This list provides a way to refer to the documentation for previous and future versions of the Dataverse Software. In order to learn more about the updates delivered from one version to another, visit the `Releases `__ page in our GitHub repo.
- pre-release `HTML (not final!) `__ and `PDF (experimental!) `__ built from the :doc:`develop ` branch :doc:`(how to contribute!) `
-- 6.4
+- 6.5
+- `6.4 `__
- `6.3 `__
- `6.2 `__
- `6.1 `__
diff --git a/modules/dataverse-parent/pom.xml b/modules/dataverse-parent/pom.xml
index 9442b55d622..9612988b3e7 100644
--- a/modules/dataverse-parent/pom.xml
+++ b/modules/dataverse-parent/pom.xml
@@ -131,7 +131,7 @@
- 6.4
+ 6.517UTF-8
From d9c03e0cf93b4cb6a8b1fadd838a3df5737d4bb4 Mon Sep 17 00:00:00 2001
From: Philip Durbin
Date: Wed, 11 Dec 2024 16:14:13 -0500
Subject: [PATCH 270/270] make change for proper tagging of images #10954
---
modules/dataverse-parent/pom.xml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/modules/dataverse-parent/pom.xml b/modules/dataverse-parent/pom.xml
index 9612988b3e7..d8105535248 100644
--- a/modules/dataverse-parent/pom.xml
+++ b/modules/dataverse-parent/pom.xml
@@ -446,8 +446,8 @@
Once the release has been made (tag created), change this back to "${parsedVersion.majorVersion}.${parsedVersion.nextMinorVersion}"
(These properties are provided by the build-helper plugin below.)
-->
- ${parsedVersion.majorVersion}.${parsedVersion.nextMinorVersion}
-
+
+ ${revision}