Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9baf2d5
File Metadata Update
stevenwinship Apr 18, 2025
6d871aa
new version checking
stevenwinship Apr 18, 2025
fd800fc
fix test
stevenwinship Apr 18, 2025
dea4904
Merge branch 'develop' into 11392-edit-file-metadata-empty-values
stevenwinship Apr 21, 2025
8dcb065
fix test
stevenwinship Apr 22, 2025
2fce5fd
add to test
stevenwinship Apr 22, 2025
26f07b7
adding info for debugging jenkins test failure
stevenwinship Apr 22, 2025
10939ce
remove jenkins debug
stevenwinship Apr 23, 2025
f5ddcff
per review comments
stevenwinship Apr 24, 2025
1615fb9
per review comments
stevenwinship Apr 24, 2025
8a57fc8
refactor to use last update timestamp instead of version number
stevenwinship Apr 25, 2025
6fedf6e
comment on data/timestamp compare
stevenwinship Apr 25, 2025
eaf49a9
refactor so both datafiles and datasets validate update timestamp the…
stevenwinship Apr 29, 2025
1320d51
refactor optional qp name from sourceInternalVersionTimestamp to sour…
stevenwinship Apr 30, 2025
b61bb1c
Merge branch 'develop' into 11392-edit-file-metadata-empty-values
stevenwinship May 29, 2025
8ef92d2
Merge branch 'develop' into 11392-edit-file-metadata-empty-values
stevenwinship Jun 24, 2025
7563cf8
Suggested doc edits (#11590)
qqmyers Jun 24, 2025
7a7a84f
Merge branch 'develop' into 11392-edit-file-metadata-empty-values
stevenwinship Jun 24, 2025
0eaca6c
Merge branch 'develop' into 11392-edit-file-metadata-empty-values
stevenwinship Jun 26, 2025
0c72a89
remove unused bundle entry
stevenwinship Jul 14, 2025
509e55a
update changelog to move this PR to 6.8
stevenwinship Jul 14, 2025
c465180
Merge branch 'develop' into 11392-edit-file-metadata-empty-values
stevenwinship Jul 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions doc/release-notes/11392-edit-file-metadata-empty-values.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
### Edit File Metadata empty values should clear data

Previously the API POST /files/{id}/metadata would ignore fields with empty values. Now the API updates the fields with the empty values essentially clearing the data. Missing fields will still be ignored.

This feature also adds a new version of the POST endpoint (/files/{id}/metadata/version/{datasetVersion}) to specify the dataset version to make the file metadata change to.

datasetVersion can either be the actual ID (12345) or the friendly version (1.0)

Note that certain fields (i.e. dataFileTags) are not versioned and changes to these will update the published as well as draft versions of the file.

See also [the guides](https://dataverse-guide--11359.org.readthedocs.build/en/11359/api/native-api.html#updating-file-metadata), #11392, and #11359.
1 change: 1 addition & 0 deletions doc/sphinx-guides/source/api/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ v6.7
----

- An undocumented :doc:`search` parameter called "show_my_data" has been removed. It was never exercised by tests and is believed to be unused. API users should use the :ref:`api-mydata` API instead.
- For POST /api/files/{id}/metadata passing an empty string (“description”:””) or array (“categories”:[]) will no longer be ignored. Empty fields will now clear out the values in the file's metadata. To ignore the fields simply do not include them in the Json string.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we trying to get this in 6.7? If not, this should be placed under "v6.8" in the changelog.

sourceInternalVersionNumber is gone right? Should we mention that?

Also, I would suggest replacing the smart quotes with straight quotes and writing "JSON" in all caps.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


v6.6
----
Expand Down
31 changes: 31 additions & 0 deletions doc/sphinx-guides/source/api/native-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4612,6 +4612,10 @@ Updating File Metadata

Updates the file metadata for an existing file where ``ID`` is the database id of the file to update or ``PERSISTENT_ID`` is the persistent id (DOI or Handle) of the file. Requires a ``jsonString`` expressing the new metadata. No metadata from the previous version of this file will be persisted, so if you want to update a specific field first get the json with the above command and alter the fields you want.

An extended version of this API will allow for the Dataset Version ID to be specified in order to modify fields of a Datafile in an already published version of the Dataset.

Note: As of Dataverse 6.7 passing in an empty value for a string field ("description":"") or an empty array for a list ("categories":[]) will clear the data for that field. In prior versions these fields would be ignored. To ignore the fields simply leave them out of the ``jsonString``.

A curl example using an ``ID``

.. code-block:: bash
Expand Down Expand Up @@ -4656,6 +4660,33 @@ Note: To update the 'tabularTags' property of file metadata, use the 'dataFileTa

Also note that dataFileTags are not versioned and changes to these will update the published version of the file.

Extended version of the API:

This extended version of the API will allow the user to modify fields of a specific version of the file's metadata.

The Dataset version id can be either the ID of the Dataset version (i.e. 12345) or the Friendly version (i.e. 1.0).

As noted above the dataFileTags are not versioned and any change to them for this version will also change them in the draft version. It is not recommended to change them with this API.

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export ID=24
export DATASET_VERSION_ID=1.0

curl -H "X-Dataverse-key:$API_TOKEN" -X POST \
-F 'jsonData={"description":"My description bbb.","provFreeform":"Test prov freeform","categories":["Data"]}' \
"$SERVER_URL/api/files/$ID/metadata/version/$DATASET_VERSION_ID"

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 \
-F 'jsonData={"description":"My description bbb.","provFreeform":"Test prov freeform","categories":["Data"]}' \
"https://demo.dataverse.org/api/files/:persistentId/metadata/version/1.0?persistentId=doi:10.5072/FK2/AAA000"

.. _EditingVariableMetadata:

Updating File Metadata Categories
Expand Down
67 changes: 67 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/api/Files.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand Down Expand Up @@ -531,7 +532,73 @@
.type(MediaType.TEXT_PLAIN) //Our plain text string is already json
.build();
}
@POST
@AuthRequired
@Path("{id}/metadata/version/{datasetVersionId}")
public Response updateFileMetadata(@Context ContainerRequestContext crc, @FormDataParam("jsonData") String jsonData,
@PathParam("id") String fileIdOrPersistentId,
@PathParam("datasetVersionId") String datasetVersionId) {
DataverseRequest req;
try {
req = createDataverseRequest(getRequestUser(crc));
} catch (Exception e) {
return error(BAD_REQUEST, "Error attempting to request information. Maybe a bad API token?");
}
final DataFile df;
FileMetadata fm = null;
try {
df = execCommand(new GetDataFileCommand(req, findDataFileOrDie(fileIdOrPersistentId)));
fm = df.getFileMetadata();
for (FileMetadata md : df.getFileMetadatas()) {
if (datasetVersionId.equals(String.valueOf(md.getDatasetVersion().getId())) ||
datasetVersionId.equals(md.getDatasetVersion().getFriendlyVersionNumber())) {
fm = md;
}
}
} catch (Exception e) {
return error(BAD_REQUEST, "Error attempting get the requested data file.");
}

try {
OptionalFileParams optionalFileParams = null;
if (jsonData != null) {
// Load optional params via JSON
optionalFileParams = new OptionalFileParams(jsonData);
} else {
return error(BAD_REQUEST, "Missing Form Data!");
}
List<FileMetadata> fmdListMinusCurrentFile = new ArrayList<>();
for (FileMetadata fileMetadata : df.getFileMetadatas()) {
if (!fileMetadata.getDataFile().equals(df)) {
fmdListMinusCurrentFile.add(fileMetadata);
}
}
jakarta.json.JsonObject jsonObject = JsonUtil.getJsonObject(jsonData);
String incomingLabel = jsonObject.getString("label", null);
String incomingDirectoryLabel = jsonObject.getString("directoryLabel", null);
String existingLabel = fm.getLabel();
String existingDirectoryLabel = fm.getDirectoryLabel();
String pathPlusFilename = IngestUtil.getPathAndFileNameToCheck(incomingLabel, incomingDirectoryLabel, existingLabel, existingDirectoryLabel);
if (IngestUtil.conflictsWithExistingFilenames(pathPlusFilename, fmdListMinusCurrentFile)) {
return error(BAD_REQUEST, BundleUtil.getStringFromBundle("files.api.metadata.update.duplicateFile", Arrays.asList(pathPlusFilename)));
}

optionalFileParams.addOptionalParams(fm);
execCommand(new UpdateDatasetVersionCommand(fm.getDataFile().getOwner(), req, fm));

} catch (DataFileTagException ex) {
return error(BAD_REQUEST, ex.getMessage());
Comment thread Fixed
} catch (Exception e) {
return error(Response.Status.INTERNAL_SERVER_ERROR, "Error updating metadata to DataFile: " + e);
}

String jsonString = fm.asGsonObject(true).toString();
return Response
.status(Response.Status.OK)
.entity("File Metadata update has been completed: " + jsonString)
.type(MediaType.TEXT_PLAIN) //Our plain text string is already json
.build();
}
@GET
@AuthRequired
@Path("{id}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,46 +194,28 @@ public boolean getTabIngest() {
return this.tabIngest;
}

public boolean hasCategories(){
if ((categories == null)||(this.categories.isEmpty())){
return false;
}
return true;
public boolean hasCategories() {
return categories != null;
}

public boolean hasFileDataTags(){
if ((dataFileTags == null)||(this.dataFileTags.isEmpty())){
return false;
}
return true;
public boolean hasFileDataTags() {
return dataFileTags != null;
}

public boolean hasDescription(){
if ((description == null)||(this.description.isEmpty())){
return false;
}
return true;
return description != null;
}

public boolean hasDirectoryLabel(){
if ((directoryLabel == null)||(this.directoryLabel.isEmpty())){
return false;
}
return true;
public boolean hasDirectoryLabel() {
return directoryLabel != null;
}

public boolean hasLabel(){
if ((label == null)||(this.label.isEmpty())){
return false;
}
return true;
public boolean hasLabel() {
return label != null;
}

public boolean hasProvFreeform(){
if ((provFreeForm == null)||(this.provFreeForm.isEmpty())){
return false;
}
return true;
public boolean hasProvFreeform() {
return provFreeForm != null;
}

public boolean hasStorageIdentifier() {
Expand All @@ -245,15 +227,15 @@ public String getStorageIdentifier() {
}

public boolean hasFileName() {
return ((fileName!=null)&&(!fileName.isEmpty()));
return fileName != null;
}

public String getFileName() {
return fileName;
}

public boolean hasMimetype() {
return ((mimeType!=null)&&(!mimeType.isEmpty()));
return mimeType != null;
}

public String getMimeType() {
Expand All @@ -266,7 +248,7 @@ public void setCheckSum(String checkSum, ChecksumType type) {
}

public boolean hasCheckSum() {
return ((checkSumValue!=null)&&(!checkSumValue.isEmpty()));
return checkSumValue != null;
}

public String getCheckSum() {
Expand Down Expand Up @@ -294,15 +276,10 @@ public void setFileSize(long fileSize) {
* @param tags
*/
public void setCategories(List<String> newCategories) {

if (newCategories != null) {
newCategories = Util.removeDuplicatesNullsEmptyStrings(newCategories);
if (newCategories.isEmpty()) {
newCategories = null;
}
this.categories = newCategories;
}

this.categories = newCategories;
}

/**
Expand Down Expand Up @@ -495,27 +472,20 @@ private void addFileDataTags(List<String> potentialTags) throws DataFileTagExcep
}

potentialTags = Util.removeDuplicatesNullsEmptyStrings(potentialTags);

if (potentialTags.isEmpty()){
return;
}


// Make a new list
this.dataFileTags = new ArrayList<>();
List<String> newList = new ArrayList<>();

// Add valid potential tags to the list
for (String tagToCheck : potentialTags){
if (DataFileTag.isDataFileTag(tagToCheck)){
this.dataFileTags.add(tagToCheck);
newList.add(tagToCheck);
}else{
String errMsg = BundleUtil.getStringFromBundle("file.addreplace.error.invalid_datafile_tag");
throw new DataFileTagException(errMsg + " [" + tagToCheck + "]. Please use one of the following: " + DataFileTag.getListofLabelsAsString());
}
}
// Shouldn't happen....
if (dataFileTags.isEmpty()){
dataFileTags = null;
}
this.dataFileTags = newList;
}

private void msg(String s){
Expand Down
Loading