diff --git a/doc/release-notes/12512-file-citation-version.md b/doc/release-notes/12512-file-citation-version.md
new file mode 100644
index 00000000000..df500c8b192
--- /dev/null
+++ b/doc/release-notes/12512-file-citation-version.md
@@ -0,0 +1,3 @@
+## API Updates
+
+- When downloading a file citation it is now possible to specify the version of the dataset. See [the guides](https://guides.dataverse.org/en/6.13/api/dataaccess.html#citation-get-citation-in-other-formats) and #12512.
\ No newline at end of file
diff --git a/doc/sphinx-guides/source/api/dataaccess.rst b/doc/sphinx-guides/source/api/dataaccess.rst
index 505556cdea6..f67bcf17336 100755
--- a/doc/sphinx-guides/source/api/dataaccess.rst
+++ b/doc/sphinx-guides/source/api/dataaccess.rst
@@ -271,8 +271,12 @@ Usage example:
export SERVER_URL=https://demo.dataverse.org
export DATAFILE_ID=99
export FORMAT=EndNote
+ export VERSION=1.0
+ export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+
+ curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/access/datafile/$DATAFILE_ID/citation/$FORMAT?version=$VERSION"
- curl "$SERVER_URL/api/access/datafile/$DATAFILE_ID/citation/$FORMAT"
+The ``version`` query parameter is optional. It will default to ``:latest-published``. See :ref:`dataset-version-specifiers` for the list of possible values. If you request a draft, an API token with access must be provided.
.. _data-variable-metadata-access:
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Access.java b/src/main/java/edu/harvard/iq/dataverse/api/Access.java
index cdb27fd5f21..b43076e1dc5 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/Access.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/Access.java
@@ -146,7 +146,9 @@ public Response datafileCitation(@Context ContainerRequestContext crc,
@Parameter(description = "Data file id or persistent identifier.", required = true)
@PathParam("fileId") String fileId,
@Parameter(description = "Citation format to return.")
- @PathParam("format") String formatString) {
+ @PathParam("format") String formatString,
+ @Parameter(description = "Dataset version, such as 1.0, :draft, :latest, or :latest-published. If omitted, uses :latest-published.", required = false)
+ @QueryParam("version") String version) {
DataCitation.Format format = DataCitation.getFormat(formatString);
if (format == null) {
@@ -158,7 +160,23 @@ public Response datafileCitation(@Context ContainerRequestContext crc,
// This will throw a ForbiddenException if access isn't authorized:
checkAuthorization(req.getUser(), df);
- String dataCitationFormatted = (new DataCitation(df.getFileMetadata())).toString(format, true, false);
+ // Default to latest file metadata.
+ FileMetadata fileMetadata = df.getFileMetadata();
+ if (version != null) {
+ try {
+ DatasetVersion datasetVersion = getDatasetVersionFromVersion(crc, df.getOwner().getId().toString(), version);
+ if (datasetVersion == null) {
+ return badRequest("Dataset version not found: " + version);
+ }
+ fileMetadata = dataFileService.findFileMetadataByDatasetVersionIdAndDataFileId(datasetVersion.getId(), df.getId());
+ if (fileMetadata == null) {
+ return badRequest("File not found in dataset version: " + version);
+ }
+ } catch (WrappedResponse wr) {
+ return wr.getResponse();
+ }
+ }
+ String dataCitationFormatted = (new DataCitation(fileMetadata)).toString(format, true, false);
return Response.ok().type(DataCitation.getCitationFormatMediaType(format, true)).entity(dataCitationFormatted).build();
}
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 ca8af25044c..d25be034197 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/FilesIT.java
@@ -4284,6 +4284,68 @@ public void testGetFileCitationFormatted() {
.statusCode(OK.getStatusCode());
}
+ @Test
+ public void testGetFileCitationFormattedWithVersion() {
+ Response createUser = UtilIT.createRandomUser();
+ createUser.then().statusCode(OK.getStatusCode());
+ String apiToken = UtilIT.getApiTokenFromResponse(createUser);
+
+ Response createDataverse = UtilIT.createRandomDataverse(apiToken);
+ createDataverse.then().statusCode(CREATED.getStatusCode());
+ String dataverseAlias = UtilIT.getAliasFromResponse(createDataverse);
+
+ Response createDataset = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken);
+ createDataset.then().statusCode(CREATED.getStatusCode());
+
+ Integer datasetId = createDataset.jsonPath().getInt("data.id");
+
+ Response uploadFile = UtilIT.uploadFileViaNative(datasetId.toString(),
+ "src/test/resources/images/coffeeshop.png", JsonUtil.createObjectBuilder().build(), apiToken);
+ uploadFile.then().statusCode(OK.getStatusCode());
+
+ String fileId = uploadFile.jsonPath().getString("data.files[0].dataFile.id");
+
+ UtilIT.publishDataverseViaNativeApi(dataverseAlias, apiToken).then().statusCode(OK.getStatusCode());
+ UtilIT.publishDatasetViaNativeApi(datasetId, "major", apiToken).then().statusCode(OK.getStatusCode());
+ UtilIT.updateFileMetadata(fileId, "{\"label\":\"renamed.png\"}", apiToken)
+ .then().statusCode(OK.getStatusCode());
+
+ // The same file has different metadata in the published version and the draft.
+ UtilIT.getFileCitationFormat(fileId, "EndNote", null, "1.0").then()
+ .statusCode(OK.getStatusCode()).body(containsString("coffeeshop.png"));
+ UtilIT.getFileCitationFormat(fileId, "EndNote", apiToken, ":draft").then()
+ .statusCode(OK.getStatusCode()).body(containsString("renamed.png"));
+ UtilIT.getFileCitationFormat(fileId, "EndNote", apiToken, ":latest").then()
+ .statusCode(OK.getStatusCode()).body(containsString("renamed.png"));
+ // No API token given so the published filename is shown
+ UtilIT.getFileCitationFormat(fileId, "EndNote", null, ":latest").then()
+ .statusCode(OK.getStatusCode()).body(containsString("coffeeshop.png"));
+ UtilIT.getFileCitationFormat(fileId, "EndNote", apiToken, ":latest-published").then()
+ .statusCode(OK.getStatusCode()).body(containsString("coffeeshop.png"));
+ UtilIT.getFileCitationFormat(fileId, "EndNote", null, ":draft").then()
+ .statusCode(UNAUTHORIZED.getStatusCode());
+
+ UtilIT.publishDatasetViaNativeApi(datasetId, "minor", apiToken).then().statusCode(OK.getStatusCode());
+ UtilIT.getFileCitationFormat(fileId, "EndNote", null, "1.1").then()
+ .statusCode(OK.getStatusCode()).body(containsString("renamed.png"));
+ // Historical citations still use the original file metadata after another release.
+ UtilIT.getFileCitationFormat(fileId, "EndNote", null, "1.0").then()
+ .statusCode(OK.getStatusCode()).body(containsString("coffeeshop.png"));
+ UtilIT.getFileCitationFormat(fileId, "RIS", null, "1.0").then()
+ .statusCode(OK.getStatusCode()).body(containsString("C1 - coffeeshop.png"));
+ UtilIT.getFileCitationFormat(fileId, "EndNote", apiToken, "666.0").then()
+ .statusCode(BAD_REQUEST.getStatusCode());
+ UtilIT.getFileCitationFormat(fileId, "EndNote", apiToken, "invalid").then()
+ .statusCode(BAD_REQUEST.getStatusCode());
+
+ Response uploadNewFile = UtilIT.uploadFileViaNative(datasetId.toString(),
+ "src/main/webapp/resources/images/dataverseproject.png", JsonUtil.createObjectBuilder().build(), apiToken);
+ uploadNewFile.then().statusCode(OK.getStatusCode());
+ String newFileId = uploadNewFile.jsonPath().getString("data.files[0].dataFile.id");
+ UtilIT.getFileCitationFormat(newFileId, "EndNote", apiToken, "1.0").then()
+ .statusCode(BAD_REQUEST.getStatusCode()).body("message", equalTo("File not found in dataset version: 1.0"));
+ }
+
// This test is disabled because it is only compatible with the containerized development environment and would cause the Jenkins job to fail.
@Test
@Disabled
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 1c1d7116dc3..106bdea16b1 100644
--- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java
+++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java
@@ -1508,10 +1508,17 @@ static Response getFileData(String fileId, String apiToken, String datasetVersio
}
static Response getFileCitationFormat(String dataFileId, String format, String apiToken) {
+ return getFileCitationFormat(dataFileId, format, apiToken, null);
+ }
+
+ static Response getFileCitationFormat(String dataFileId, String format, String apiToken, String version) {
RequestSpecification request = given();
if (apiToken != null) {
request.header(API_TOKEN_HTTP_HEADER, apiToken);
}
+ if (version != null) {
+ request.queryParam("version", version);
+ }
return request.get("/api/access/datafile/" + dataFileId + "/citation/" + format);
}