DS_VERSION_RESERVED_IDENTIFIERS = List.of(DS_VERSION_DRAFT, DS_VERSION_LATEST_PUBLISHED, DS_VERSION_LATEST);
+ // TODO: should be replaced by a bundle reference
+ public static final String DS_VERSION_IDENTIFIER_MESSAGE = "version must be one of :latest, :latest-published, :draft, or a numeric version like 1.0";
// addFiles call
public static final String API_ADD_FILES_COUNT_PROCESSED = "Total number of files";
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 136b6dbb69b..3635ce97867 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java
@@ -6,7 +6,9 @@
import edu.harvard.iq.dataverse.api.auth.AuthRequired;
import edu.harvard.iq.dataverse.api.dto.CustomTermsDTO;
import edu.harvard.iq.dataverse.api.dto.LicenseUpdateRequest;
+import edu.harvard.iq.dataverse.api.dto.MultiDatasetExportRequest;
import edu.harvard.iq.dataverse.api.dto.RoleAssignmentDTO;
+import edu.harvard.iq.dataverse.api.util.JsonResponseBuilder;
import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean;
import edu.harvard.iq.dataverse.authorization.DataverseRole;
import edu.harvard.iq.dataverse.authorization.Permission;
@@ -63,6 +65,9 @@
import edu.harvard.iq.dataverse.workflow.WorkflowContext;
import edu.harvard.iq.dataverse.workflow.WorkflowContext.TriggerType;
import edu.harvard.iq.dataverse.workflow.WorkflowServiceBean;
+import io.gdcc.spi.export.ExportException;
+import io.gdcc.spi.export.Exporter;
+import io.gdcc.spi.export.MultiDatasetExporter;
import jakarta.ejb.EJB;
import jakarta.ejb.EJBException;
import jakarta.inject.Inject;
@@ -70,6 +75,7 @@
import jakarta.json.stream.JsonParsingException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.core.*;
@@ -79,6 +85,7 @@
import org.apache.logging.log4j.util.Strings;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.media.Content;
+import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
@@ -115,6 +122,7 @@
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.FORBIDDEN;
+import static jakarta.ws.rs.core.Response.Status.NOT_IMPLEMENTED;
@Path("datasets")
public class Datasets extends AbstractApiBean {
@@ -292,6 +300,120 @@ public Response exportDataset(@Context ContainerRequestContext crc, @QueryParam(
return error(Response.Status.FORBIDDEN, "Export Failed");
}
}
+
+ /**
+ * Exports metadata for one or multiple dataset versions based on a request containing persistent identifiers
+ * and version specifications.
+ *
+ * This method validates the exporter, checks for supported multi-dataset exports when applicable,
+ * resolves dataset and version references, ensures access permissions, and aggregates errors encountered
+ * during resolution before proceeding with export (if no errors occurred).
+ *
+ * {@code @AuthRequested} triggers {@link edu.harvard.iq.dataverse.api.auth.AuthFilter} to inject a user entity
+ * into the {@code ContainerRequestContext}.
+ *
+ * @param request the request body containing the exporter name and a list of dataset export specifications
+ * (persistent ID and version), using JSON-B to inject and Bean Validation to vet.
+ * @param uriInfo contextual information about the request URI
+ * @param headers HTTP headers of the request
+ * @param crc container request context providing access to the authenticated user and other request-scoped data
+ * @return a JAX-RS Response indicating success, client errors (e.g., invalid or unsupported request), or (TODO)
+ * @implNote In regard to the HTTP method: as it is retrieval of data, it should be GET, not POST.
+ * A GET request would become a nightmare to code on the client side fast with the amount of parameters.
+ * A nice concise JSON format is much easier to handle.
+ * But, GET should have no body according to RFC 9110, yet the QUERY method is still a draft
+ * (https://httpwg.org/http-extensions/draft-ietf-httpbis-safe-method-w-body.html).
+ * Thus, POST is the only viable option (https://roy.gbiv.com/untangled/2009/it-is-okay-to-use-post).
+ */
+ @POST
+ @AuthRequired
+ @Consumes("application/json")
+ @Path("export")
+ @Operation(summary = "Export metadata for one or multiple dataset versions")
+ @APIResponse(responseCode = "400", description = "Invalid JSON or not following schema (syntactical error), or valid JSON, but not processable (semantical error).")
+ public Response exportMultiple(@RequestBody(content = @Content(schema = @Schema(implementation = MultiDatasetExportRequest.class)))
+ @Valid MultiDatasetExportRequest request,
+ @Context UriInfo uriInfo, @Context HttpHeaders headers, @Context ContainerRequestContext crc) {
+ // Verify the exporter exists and supports multiple datasets (when given more than one)
+ try {
+ Exporter exporter = ExportService.getInstance().getExporter(request.exporter());
+ if (request.datasets().size() > 1 && !(exporter instanceof MultiDatasetExporter)) {
+ return error(BAD_REQUEST, "Requested exporter "+request.exporter()+" does not support multi-dataset exports");
+ }
+ } catch (ExportException e) {
+ return error(BAD_REQUEST, "No such exporter "+request.exporter());
+ }
+ // Lookup the HTTP request enhanced with the authenticated user from context (resolved by the AuthFilter/AuthRequired mechanism)
+ DataverseRequest userScopedHttpRequest = createDataverseRequest(getRequestUser(crc));
+
+ // Instead of bailing out on the first error, keep going and record any problems before reporting back
+ List errors = new ArrayList<>();
+ // Note: the set automatically avoids duplicate requests
+ Set versions = new HashSet<>();
+
+ // Get all the requested DatasetVersions (requiring permission checks)
+ for (MultiDatasetExportRequest.ExportItem requested : request.datasets()) {
+ // First, lookup the dataset itself to verify it exists
+ Dataset dataset = datasetService.findByGlobalId(requested.persistentId());
+ if (dataset == null) {
+ // Record if absent and move on to next request
+ errors.add("No such dataset " + requested.persistentId());
+ continue;
+ }
+
+ // Second, if the requested version is a version number (and not a "draft", "latest published" or
+ // "latest" (= draft or latest published)), lookup the version number of the "latest published"
+ // for comparison. Reason: Only this latest-published version is cached and supportable for retrieval!
+ if (!DS_VERSION_RESERVED_IDENTIFIERS.contains(requested.version())) {
+ try {
+ // Note: this lookup includes a permission check for the user being able to access the lastest published version
+ DatasetVersion latestPublished = commandEngine.submit(new GetLatestPublishedDatasetVersionCommand(userScopedHttpRequest, dataset));
+ // Specific (published) version requested by id but none found
+ if (latestPublished == null) {
+ // Record error and move on to next request
+ errors.add("Dataset %s has no published versions, try %s.".formatted(requested.persistentId(), DS_VERSION_DRAFT));
+ continue;
+ // Specific (published) version requested by does not match the "latest-published" one
+ } else if (!requested.version().equals(latestPublished.getFriendlyVersionNumber())) {
+ // Record error and move on to next request
+ errors.add("Requested version number %s for dataset %s is not supported for export, only %s (equivalent to %s)."
+ .formatted(requested.version(), requested.persistentId(), latestPublished.getFriendlyVersionNumber(), DS_VERSION_LATEST_PUBLISHED));
+ continue;
+ }
+ } catch (CommandException e) {
+ // For a command exception to appear, something must have been going very wrong here.
+ // Only report an internal error appeared, for security details in logs only.
+ String incidentId = UUID.randomUUID().toString();
+ String message = "While looking up %s's version %s, an internal error occured. incidentId=%s"
+ .formatted(requested.persistentId(), requested.version(), incidentId);
+ errors.add(message);
+ logger.log(Level.WARNING, message, e);
+ // Now move on to the next request
+ continue;
+ }
+ }
+
+ try {
+ // Third, lookup the dataset version (potentially again) as requested.
+ // Note: this lookup includes the permission check for the user being able to access the requested version
+ versions.add(getDatasetVersionOrDie(userScopedHttpRequest, requested.version(), dataset, uriInfo, headers));
+ } catch (WrappedResponse e) {
+ // If not found (most likely because not authorized), record an error and move on to the next request
+ errors.add("Unable to look up dataset %s based on version %s. Try %s."
+ .formatted(requested.persistentId(), requested.version(), DS_VERSION_LATEST_PUBLISHED));
+ }
+ }
+
+ // In case of errors, stop here. Otherwise, hand over to ExportService.
+ if (!errors.isEmpty()) {
+ return JsonResponseBuilder.error(BAD_REQUEST)
+ .message("The following errors were found in your request:\n" + StringUtils.join("\n", errors))
+ .build();
+ }
+
+ // TODO: hand over to ExportService to export all the datasets now
+ return error(NOT_IMPLEMENTED, "TODO");
+ }
@DELETE
@AuthRequired
@@ -463,8 +585,8 @@ public Response listVersions(@Context ContainerRequestContext crc, @PathParam("i
return response( req -> {
Dataset dataset = findDatasetOrDie(id);
- Boolean deepLookup = excludeFiles == null ? true : !excludeFiles;
- Boolean includeMetadataBlocks = excludeMetadataBlocks == null ? true : !excludeMetadataBlocks;
+ Boolean deepLookup = excludeFiles == null || !excludeFiles;
+ Boolean includeMetadataBlocks = excludeMetadataBlocks == null || !excludeMetadataBlocks;
return ok( execCommand( new ListVersionsCommand(req, dataset, offset, limit, deepLookup) )
.stream()
@@ -487,8 +609,8 @@ public Response getVersion(@Context ContainerRequestContext crc,
@Context UriInfo uriInfo,
@Context HttpHeaders headers) {
return response( req -> {
- boolean includeMetadataBlocks = excludeMetadataBlocks == null ? true : !excludeMetadataBlocks;
- boolean includeFiles = excludeFiles == null ? true : !excludeFiles;
+ boolean includeMetadataBlocks = excludeMetadataBlocks == null || !excludeMetadataBlocks;
+ boolean includeFiles = excludeFiles == null || !excludeFiles;
boolean ignoreSettingExcludeEmailFromExport = ignoreSettingToExcludeEmailFromExport != null ? ignoreSettingToExcludeEmailFromExport : false;
//If excludeFiles is null the default is to provide the files and because of this we need to check permissions.
@@ -785,7 +907,7 @@ public Response updateDatasetPIDMetadata(@Context ContainerRequestContext crc, @
return response(req -> {
Dataset dataset = findDatasetOrDie(id);
execCommand(new UpdateDvObjectPIDMetadataCommand(dataset, req));
- List args = Arrays.asList(dataset.getIdentifier());
+ List args = Collections.singletonList(dataset.getIdentifier());
return ok(BundleUtil.getStringFromBundle("datasets.api.updatePIDMetadata.success.for.single.dataset", args));
}, getRequestUser(crc));
}
@@ -1308,7 +1430,7 @@ public Response publishDataset(@Context ContainerRequestContext crc, @PathParam(
successMsg = BundleUtil.getStringFromBundle("datasetversion.archive.inprogress");
} catch (CommandException ex) {
successMsg = BundleUtil.getStringFromBundle("datasetversion.update.archive.failure")
- + " - " + ex.toString();
+ + " - " + ex;
logger.severe(ex.getMessage());
}
} else if (status.equals(DatasetVersion.ARCHIVAL_STATUS_SUCCESS)) {
@@ -1318,7 +1440,7 @@ public Response publishDataset(@Context ContainerRequestContext crc, @PathParam(
}
}
} catch (CommandException ex) {
- errorMsg = BundleUtil.getStringFromBundle("datasetversion.update.failure") + " - " + ex.toString();
+ errorMsg = BundleUtil.getStringFromBundle("datasetversion.update.failure") + " - " + ex;
logger.severe(ex.getMessage());
}
if (errorMsg != null) {
@@ -1389,7 +1511,7 @@ public Response publishMigratedDataset(@Context ContainerRequestContext crc, Str
}
// Release User is only set in FinalizeDatasetPublicationCommand if the pub date
// is null, so set it here.
- ds.setReleaseUser((AuthenticatedUser) user);
+ ds.setReleaseUser(user);
}
} catch (Exception e) {
logger.fine(e.getMessage());
@@ -1419,7 +1541,7 @@ public Response publishMigratedDataset(@Context ContainerRequestContext crc, Str
ds = commandEngine.submit(cmd);
}
} catch (CommandException ex) {
- errorMsg = BundleUtil.getStringFromBundle("datasetversion.update.failure") + " - " + ex.toString();
+ errorMsg = BundleUtil.getStringFromBundle("datasetversion.update.failure") + " - " + ex;
logger.severe(ex.getMessage());
}
@@ -2224,7 +2346,7 @@ public Response createAssignment(@Context ContainerRequestContext crc, RoleAssig
// concurrent update
return error(Status.CONFLICT, BundleUtil.getStringFromBundle("datasets.api.grant.role.assignee.has.role.error"));
}
- List args = Arrays.asList(ex.getMessage());
+ List args = Collections.singletonList(ex.getMessage());
logger.log(Level.WARNING, BundleUtil.getStringFromBundle("datasets.api.grant.role.cant.create.assignment.error", args));
return ex.getResponse();
}
@@ -2246,7 +2368,7 @@ public Response deleteAssignment(@Context ContainerRequestContext crc, @PathPara
return ex.getResponse();
}
} else {
- List args = Arrays.asList(Long.toString(assignmentId));
+ List args = List.of(Long.toString(assignmentId));
return error(Status.NOT_FOUND, BundleUtil.getStringFromBundle("datasets.api.revoke.role.not.found.error", args));
}
}
@@ -2507,7 +2629,7 @@ public Response receiveChecksumValidationResults(@Context ContainerRequestContex
ImportMode importMode = ImportMode.MERGE;
try {
- JsonObject jsonFromImportJobKickoff = execCommand(new ImportFromFileSystemCommand(createDataverseRequest(getRequestUser(crc)), dataset, uploadFolder, new Long(totalSize), importMode));
+ JsonObject jsonFromImportJobKickoff = execCommand(new ImportFromFileSystemCommand(createDataverseRequest(getRequestUser(crc)), dataset, uploadFolder, Long.valueOf(totalSize), importMode));
long jobId = jsonFromImportJobKickoff.getInt("executionId");
String message = jsonFromImportJobKickoff.getString("message");
JsonObjectBuilder job = Json.createObjectBuilder();
@@ -2525,7 +2647,7 @@ public Response receiveChecksumValidationResults(@Context ContainerRequestContex
//Where the lifting is actually done, moving the s3 files over and having dataverse know of the existance of the package
s3PackageImporter.copyFromS3(dataset, uploadFolder);
- DataFile packageFile = s3PackageImporter.createPackageDataFile(dataset, uploadFolder, new Long(totalSize));
+ DataFile packageFile = s3PackageImporter.createPackageDataFile(dataset, uploadFolder, Long.valueOf(totalSize));
if (packageFile == null) {
logger.log(Level.SEVERE, "S3 File package import failed.");
@@ -2567,7 +2689,7 @@ public Response receiveChecksumValidationResults(@Context ContainerRequestContex
} else if ("validation failed".equals(statusMessageFromDcm)) {
Map distinctAuthors = permissionService.getDistinctUsersWithPermissionOn(Permission.EditDataset, dataset);
distinctAuthors.values().forEach((value) -> {
- userNotificationService.sendNotification((AuthenticatedUser) value, new Timestamp(new Date().getTime()), UserNotification.Type.CHECKSUMFAIL, dataset.getId());
+ userNotificationService.sendNotification(value, new Timestamp(new Date().getTime()), UserNotification.Type.CHECKSUMFAIL, dataset.getId());
});
List superUsers = authenticationServiceBean.findSuperUsers();
if (superUsers != null && !superUsers.isEmpty()) {
@@ -2774,7 +2896,7 @@ public Response getMPUploadUrls(@Context ContainerRequestContext crc, @PathParam
int uploadedFileCount = datasetService.getDataFileCountByOwner(dataset.getId());
if (uploadedFileCount >= effectiveDatasetFileCountLimit) {
return error(Response.Status.BAD_REQUEST,
- BundleUtil.getStringFromBundle("file.add.count_exceeds_limit", Arrays.asList(String.valueOf(effectiveDatasetFileCountLimit))));
+ BundleUtil.getStringFromBundle("file.add.count_exceeds_limit", Collections.singletonList(String.valueOf(effectiveDatasetFileCountLimit))));
}
}
}
@@ -3471,9 +3593,9 @@ public Response listLocks(@Context ContainerRequestContext crc, @QueryParam("typ
StringJoiner reasonJoiner = new StringJoiner(", ");
for (Reason r: Reason.values()) {
reasonJoiner.add(r.name());
- };
+ }
String errorMessage = "Invalid lock type value: " + lockType +
- "; valid lock types: " + reasonJoiner.toString();
+ "; valid lock types: " + reasonJoiner;
return error(Response.Status.BAD_REQUEST, errorMessage);
}
}
@@ -3943,13 +4065,11 @@ public Response getTimestamps(@Context ContainerRequestContext crc, @PathParam("
// If the modification/permissionmodification time is
// set and the index time is null or is before the mod time, the relevant index is stale
timestamps.add("hasStaleIndex",
- (dataset.getModificationTime() != null && (dataset.getIndexTime() == null
- || (dataset.getIndexTime().compareTo(dataset.getModificationTime()) <= 0))) ? true
- : false);
+ dataset.getModificationTime() != null && (dataset.getIndexTime() == null
+ || (dataset.getIndexTime().compareTo(dataset.getModificationTime()) <= 0)));
timestamps.add("hasStalePermissionIndex",
- (dataset.getPermissionModificationTime() != null && (dataset.getIndexTime() == null
- || (dataset.getIndexTime().compareTo(dataset.getModificationTime()) <= 0))) ? true
- : false);
+ dataset.getPermissionModificationTime() != null && (dataset.getIndexTime() == null
+ || (dataset.getIndexTime().compareTo(dataset.getModificationTime()) <= 0)));
}
// More detail if you can see a draft
if (canSeeDraft) {
@@ -4496,12 +4616,11 @@ public Response requestGlobusDownload(@Context ContainerRequestContext crc, @Pat
id = ((JsonString) fileVal).getString();
break;
case NUMBER:
- id = ((JsonNumber) fileVal).toString();
+ id = fileVal.toString();
break;
default:
return badRequest("fileIds must be numeric or string (ids/PIDs)");
}
- ;
fileIds.add(id);
}
} else {
@@ -4901,7 +5020,7 @@ public Response updateMultipleFileMetadata(@Context ContainerRequestContext crc,
if (IngestUtil.conflictsWithExistingFilenames(pathPlusFilename, fmdListMinusCurrentFile)) {
return error(BAD_REQUEST, BundleUtil.getStringFromBundle("files.api.metadata.update.duplicateFile",
- Arrays.asList(pathPlusFilename)));
+ Collections.singletonList(pathPlusFilename)));
}
// Apply optional params
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/dto/MultiDatasetExportRequest.java b/src/main/java/edu/harvard/iq/dataverse/api/dto/MultiDatasetExportRequest.java
new file mode 100644
index 00000000000..df70cf0547a
--- /dev/null
+++ b/src/main/java/edu/harvard/iq/dataverse/api/dto/MultiDatasetExportRequest.java
@@ -0,0 +1,67 @@
+package edu.harvard.iq.dataverse.api.dto;
+
+import edu.harvard.iq.dataverse.api.ApiConstants;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import jakarta.validation.constraints.Size;
+
+import java.util.List;
+
+/**
+ * Request payload for exporting multiple datasets with a single exporter.
+ *
+ * Each request identifies the exporter to use and a list of dataset selections.
+ * Every dataset selection couples a persistent identifier with a version
+ * identifier so the server can resolve the exact dataset version to export.
+ *
+ *
This request model is intended for JSON request bodies sent to a bulk or
+ * multi-dataset export endpoint.
+ *
+ * @param exporter the name or identifier of the exporter to use; must not be blank
+ * @param datasets the datasets to export; must not be {@code null} and at least have 1 element.
+ * The list is defensively copied into an unmodifiable snapshot.
+ */
+public record MultiDatasetExportRequest(
+ @NotBlank String exporter,
+ @NotNull @Size(min = 1) List datasets
+) {
+ /**
+ * Creates a new multi-dataset export request. The supplied dataset list is copied to preserve
+ * immutability and prevent later external modification of the request contents.
+ * @throws NullPointerException if {@code datasets} is {@code null}
+ */
+ public MultiDatasetExportRequest {
+ // Make sure to create a readonly copy, but keep null around to have bean validation catch it and complain later
+ datasets = datasets == null ? null : List.copyOf(datasets);
+ }
+
+ /**
+ * A single dataset selection within a {@link MultiDatasetExportRequest}.
+ *
+ * Each item identifies one dataset to export by its persistent identifier and the specific version to resolve.
+ * The version may be one of the symbolic dataset version identifiers supported by the API, or a numeric version
+ * identifier, as validated by {@link ApiConstants#DS_VERSION_IDENTIFIER_REGEX}.
+ *
+ * @param persistentId the persistent identifier of the dataset to export; must not be blank
+ * @param version the dataset version identifier to export;
+ * must not be blank and must match the API-supported dataset version syntax
+ */
+ public record ExportItem(
+ @NotBlank String persistentId,
+ @NotBlank
+ @Pattern(
+ regexp = ApiConstants.DS_VERSION_IDENTIFIER_REGEX,
+ // TODO: replace message with bundle reference
+ message = ApiConstants.DS_VERSION_IDENTIFIER_MESSAGE
+ )
+ String version
+ ) {
+ // If omitted or blank, apply a default version to look up (aligned with GET endpoint behavior)
+ public ExportItem {
+ if (version == null || version.isBlank()) {
+ version = ApiConstants.DS_VERSION_LATEST_PUBLISHED;
+ }
+ }
+ }
+}
diff --git a/src/main/java/edu/harvard/iq/dataverse/api/util/JsonResponseBuilder.java b/src/main/java/edu/harvard/iq/dataverse/api/util/JsonResponseBuilder.java
index 287a99270e9..dab6039b8cf 100644
--- a/src/main/java/edu/harvard/iq/dataverse/api/util/JsonResponseBuilder.java
+++ b/src/main/java/edu/harvard/iq/dataverse/api/util/JsonResponseBuilder.java
@@ -229,10 +229,9 @@ public JsonResponseBuilder log(Logger logger, Level level, Optional e
metadata.deleteCharAt(metadata.length()-1);
if (ex.isPresent()) {
- ex.get().printStackTrace();
metadata.append("|");
logger.log(level, metadata.toString(), ex);
- if(includeStackTrace) {
+ if (includeStackTrace) {
logger.log(level, ExceptionUtils.getStackTrace(ex.get()));
}
} else {
diff --git a/src/main/java/edu/harvard/iq/dataverse/export/InternalExportDataProvider.java b/src/main/java/edu/harvard/iq/dataverse/export/InternalExportDataProvider.java
index f0d77eb8b52..cab9d89de73 100644
--- a/src/main/java/edu/harvard/iq/dataverse/export/InternalExportDataProvider.java
+++ b/src/main/java/edu/harvard/iq/dataverse/export/InternalExportDataProvider.java
@@ -1,22 +1,31 @@
package edu.harvard.iq.dataverse.export;
-import java.io.InputStream;
-import java.util.Optional;
-
-import jakarta.json.Json;
-import jakarta.json.JsonArray;
-import jakarta.json.JsonArrayBuilder;
-import jakarta.json.JsonObject;
-import jakarta.json.JsonObjectBuilder;
import edu.harvard.iq.dataverse.DataCitation;
import edu.harvard.iq.dataverse.DataFile;
import edu.harvard.iq.dataverse.DatasetVersion;
import edu.harvard.iq.dataverse.FileMetadata;
import edu.harvard.iq.dataverse.pidproviders.doi.datacite.DOIDataCiteRegisterService;
-import io.gdcc.spi.export.ExportDataProvider;
import edu.harvard.iq.dataverse.util.bagit.OREMap;
import edu.harvard.iq.dataverse.util.json.JsonPrinter;
import edu.harvard.iq.dataverse.util.json.JsonUtil;
+import io.gdcc.spi.export.DatasetExportQuery;
+import io.gdcc.spi.export.ExportDataProvider;
+import io.gdcc.spi.export.FileExportQuery;
+import io.gdcc.spi.export.PageRequest;
+import jakarta.json.Json;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonArrayBuilder;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonObjectBuilder;
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.util.Optional;
+import java.util.stream.Stream;
/**
* Provides all data necessary to create an export
@@ -39,6 +48,11 @@ public class InternalExportDataProvider implements ExportDataProvider {
this.is=is;
}
+ @Override
+ public JsonObject getDatasetJson(DatasetExportQuery datasetExportQuery) {
+ return getDatasetJson();
+ }
+
@Override
public JsonObject getDatasetJson() {
if (jsonRepresentation == null) {
@@ -47,7 +61,15 @@ public JsonObject getDatasetJson() {
}
return jsonRepresentation;
}
-
+
+ /**
+ * Needs a better implementation, as it should replace the deprecated method.
+ */
+ @Override
+ public JsonObject getDatasetSchemaDotOrg(DatasetExportQuery datasetExportQuery) {
+ return getDatasetSchemaDotOrg();
+ }
+
@Override
public JsonObject getDatasetSchemaDotOrg() {
if (schemaDotOrgRepresentation == null) {
@@ -56,7 +78,15 @@ public JsonObject getDatasetSchemaDotOrg() {
}
return schemaDotOrgRepresentation;
}
-
+
+ /**
+ * Needs a better implementation, as it should replace the deprecated method.
+ */
+ @Override
+ public JsonObject getDatasetORE(DatasetExportQuery datasetExportQuery) {
+ return getDatasetORE();
+ }
+
@Override
public JsonObject getDatasetORE() {
if (oreRepresentation == null) {
@@ -64,13 +94,63 @@ public JsonObject getDatasetORE() {
}
return oreRepresentation;
}
-
+
+ /**
+ * Needs a better implementation, as it should replace the deprecated method.
+ */
+ @Override
+ public Document getDataCiteXml(DatasetExportQuery datasetExportQuery) {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ try
+ {
+ String dataCiteXml = getDataCiteXml();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ return builder.parse(new InputSource(new StringReader(dataCiteXml)));
+ // TODO: remove this anti-pattern of catcha-all
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
@Override
public String getDataCiteXml() {
return DOIDataCiteRegisterService.getMetadataFromDvObject(
dv.getDataset().getGlobalId().asString(), new DataCitation(dv).getDataCiteMetadata(), dv.getDataset());
}
+ /**
+ * Needs a better implementation, as it should replace the deprecated method.
+ * This will trigger all sorts of N+1 query expansions, it would be much better to put the
+ * lookup in a factory method instead of on-demand when the exporter requests it.
+ * It does not at all filter anything as may be requested.
+ */
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery) {
+ return dv.getFileMetadatas()
+ .stream()
+ .map(fileMetadata -> {
+ DataFile dataFile = fileMetadata.getDataFile();
+ return JsonPrinter.json(dataFile, fileMetadata, true).build();
+ });
+ }
+
+ /**
+ * Needs a better implementation, as it should replace the deprecated method.
+ * This will trigger all sorts of N+1 query expansions, it would be much better to put the
+ * lookup in a factory method instead of on-demand when the exporter requests it.
+ * It does not at all filter anything as may be requested.
+ */
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery, PageRequest pageRequest) {
+ return dv.getFileMetadatas().subList(pageRequest.getOffset(), pageRequest.getOffset() + pageRequest.getLimit())
+ .stream()
+ .map(fileMetadata -> {
+ DataFile dataFile = fileMetadata.getDataFile();
+ return JsonPrinter.json(dataFile, fileMetadata, true).build();
+ });
+ }
+
@Override
public JsonArray getDatasetFileDetails() {
JsonArrayBuilder jab = Json.createArrayBuilder();
diff --git a/src/main/java/edu/harvard/iq/dataverse/export/citation/BibTex.java b/src/main/java/edu/harvard/iq/dataverse/export/citation/BibTex.java
new file mode 100644
index 00000000000..d3732dfec91
--- /dev/null
+++ b/src/main/java/edu/harvard/iq/dataverse/export/citation/BibTex.java
@@ -0,0 +1,263 @@
+package edu.harvard.iq.dataverse.export.citation;
+
+import com.google.auto.service.AutoService;
+import io.gdcc.spi.export.DatasetExportQuery;
+import io.gdcc.spi.export.ExportDataProvider;
+import io.gdcc.spi.export.ExportException;
+import io.gdcc.spi.export.Exporter;
+import io.gdcc.spi.export.FileExportQuery;
+import io.gdcc.spi.export.FileMetadataPredicates;
+import io.gdcc.spi.export.MultiDatasetExporter;
+import io.gdcc.spi.meta.annotations.DataversePlugin;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+import java.time.Year;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+
+@DataversePlugin
+// TODO: The @AutoService may be removed at a later stage when we can load from main classpath what is annotated with
+// @DataversePlugin without conflicts. Leaving this in for now to continue developing.
+@AutoService(Exporter.class)
+public class BibTex implements Exporter, MultiDatasetExporter {
+
+ @Override
+ public String getFormatName() {
+ return "bibtex";
+ }
+
+ @Override
+ public String getDisplayName(Locale locale) {
+ return "BibTex";
+ }
+
+ @Override
+ public Boolean isHarvestable() {
+ return false;
+ }
+
+ @Override
+ public Boolean isAvailableToUsers() {
+ return true;
+ }
+
+ @Override
+ public String getMediaType() {
+ return "text/x-bibtex";
+ }
+
+ @Override
+ public void exportDataset(ExportDataProvider provider, OutputStream outputStream) throws ExportException {
+ exportMultiple(List.of(provider), outputStream);
+ }
+
+ @Override
+ public void exportMultiple(List list, OutputStream outputStream) throws ExportException {
+ try (
+ OutputStreamWriter streamWriter = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
+ BufferedWriter writer = new BufferedWriter(streamWriter)
+ ) {
+ for (ExportDataProvider provider : list) {
+ convert(provider, writer);
+ writer.flush();
+ }
+ } catch (IOException e) {
+ throw new ExportException("Failed to export datasets as BibTeX", e);
+ }
+ }
+
+ private void convert(ExportDataProvider provider, BufferedWriter writer) throws IOException {
+ DatasetExportQuery query = DatasetExportQuery.builder()
+ .fileQuery(FileExportQuery.builder().filePredicates(FileMetadataPredicates.SKIP_FILES).build())
+ .build();
+ Document dataciteXml = provider.getDataCiteXml(query);
+ MetaData metadata = new MetaData(dataciteXml);
+ convert(metadata, writer);
+ }
+
+ private static final String X_IDENTIFIER = "/resource/identifier[@identifierType='DOI']";
+ private static final String X_PUBLICATION_YEAR = "/resource/publicationYear";
+ private static final String X_RESOURCE_TYPE_GENERAL = "/resource/resourceType/@resourceTypeGeneral";
+ private static final String X_TITLE = "/resource/titles/title[1]";
+ private static final String X_PUBLISHER = "/resource/publisher";
+ private static final String X_VERSION = "/resource/version";
+ private static final String X_CREATORS = "/resource/creators/creator";
+
+ private static final String X_CREATOR_NAME = "creatorName";
+ private static final String X_GIVEN_NAME = "givenName";
+ private static final String X_FAMILY_NAME = "familyName";
+
+ private void convert(MetaData metadata, BufferedWriter writer) throws IOException {
+
+ String year = metadata.string(X_PUBLICATION_YEAR);
+ String resourceTypeGeneral = metadata.string(X_RESOURCE_TYPE_GENERAL);
+ String doi = metadata.string(X_IDENTIFIER);
+
+ // initial line with BibTex type and reference identifier
+ writer.write("@%s{%s_%s,".formatted(
+ mapToBibTexType(resourceTypeGeneral),
+ pidToBibKey(doi),
+ year == null || year.isBlank() ? Year.now() : year));
+ writer.newLine();
+
+ String title = metadata.string(X_TITLE);
+ writeBibTexField(writer, "title", title == null || title.isBlank() ? null : "{" + escapePairedQuotes(title) + "}");
+
+ List authors = extractAuthors(metadata);
+ writeBibTexField(writer, "author", authors.isEmpty() ? null : String.join(" and ", authors));
+
+ String publisher = metadata.string(X_PUBLISHER);
+ writeBibTexField(writer, "publisher", publisher);
+
+ String version = metadata.string(X_VERSION);
+ writeBibTexField(writer, "version", version);
+
+ /* TODO:
+ out.write("url = {");
+ out.write(persistentId.asURL());
+ out.write("}\r\n");
+ */
+
+ writeBibTexField(writer, "year", year);
+ writeBibTexLastField(writer, "doi", doi);
+
+ writer.write("}");
+ writer.newLine();
+ }
+
+ private static String escapePairedQuotes(String string) {
+ return string == null ? "" : string.replaceAll("\"([^\"]*)\"", "``$1''");
+ }
+
+ private static List extractAuthors(MetaData metadata) {
+ NodeList creatorNodes = metadata.nodes(X_CREATORS);
+ List authors = new ArrayList<>();
+
+ for (int i = 0; i < creatorNodes.getLength(); i++) {
+ Node creator = creatorNodes.item(i);
+
+ String familyName = metadata.string(creator, X_FAMILY_NAME);
+ String givenName = metadata.string(creator, X_GIVEN_NAME);
+ String creatorName = metadata.string(creator, X_CREATOR_NAME);
+
+ if (familyName != null && !familyName.isBlank() && givenName != null && !givenName.isBlank()) {
+ authors.add(familyName + ", " + givenName);
+ } else if (creatorName != null && !creatorName.isBlank()) {
+ authors.add(creatorName);
+ }
+ }
+
+ return authors;
+ }
+
+ private static String pidToBibKey(String pid) {
+ if (pid == null || pid.isBlank()) {
+ return "unknown";
+ }
+ return pid.replaceAll("\\W+", "_");
+ }
+
+ private static String mapToBibTexType(String resourceTypeGeneral) {
+ if (resourceTypeGeneral == null || resourceTypeGeneral.isBlank()) {
+ return "misc";
+ }
+ // TODO: this should be made configurable, so more mappings can be done
+ return switch (resourceTypeGeneral) {
+ case "Dataset" -> "dataset";
+ case "Software" -> "software";
+ default -> "misc";
+ };
+ }
+
+ private static void writeBibTexField(BufferedWriter writer, String name, String value) throws IOException {
+ if (value == null || value.isBlank()) {
+ return;
+ }
+ writer.write(" " + name + " = {" + value + "},");
+ writer.newLine();
+ }
+
+ private static void writeBibTexLastField(BufferedWriter writer, String name, String value) throws IOException {
+ if (value == null || value.isBlank()) {
+ return;
+ }
+ writer.write(" " + name + " = {" + value + "}");
+ writer.newLine();
+ }
+
+ // TODO: This might be very useful to include in the Exporter SPI package, as it's a nice helper for everyone
+ private static final class MetaData {
+
+ private static final String DATACITE_NS = "http://datacite.org/schema/kernel-4";
+ private final Document dataCiteXml;
+ private final XPath xPath;
+
+ MetaData(Document dataCiteXml) {
+ this.dataCiteXml = dataCiteXml;
+ this.xPath = createXPath();
+ }
+
+ static XPath createXPath() {
+ XPath xPath = XPathFactory.newInstance().newXPath();
+ xPath.setNamespaceContext(new NamespaceContext() {
+ @Override
+ public String getNamespaceURI(String prefix) {
+ if ("d".equals(prefix)) {
+ return DATACITE_NS;
+ }
+ if ("xml".equals(prefix)) {
+ return XMLConstants.XML_NS_URI;
+ }
+ return XMLConstants.NULL_NS_URI;
+ }
+
+ @Override
+ public String getPrefix(String namespaceURI) {
+ return null;
+ }
+
+ @Override
+ public Iterator getPrefixes(String namespaceURI) {
+ return List.of().iterator();
+ }
+ });
+ return xPath;
+ }
+
+ String string(String expression) {
+ return string(dataCiteXml, expression);
+ }
+
+ String string(Object item, String expression) {
+ try {
+ String value = (String) xPath.evaluate(expression, item, XPathConstants.STRING);
+ return value == null ? null : value.trim();
+ } catch (XPathExpressionException e) {
+ throw new IllegalStateException("Invalid XPath expression: " + expression, e);
+ }
+ }
+
+ NodeList nodes(String expression) {
+ try {
+ return (NodeList) xPath.evaluate(expression, dataCiteXml, XPathConstants.NODESET);
+ } catch (XPathExpressionException e) {
+ throw new IllegalStateException("Invalid XPath expression: " + expression, e);
+ }
+ }
+ }
+}
diff --git a/src/test/java/edu/harvard/iq/dataverse/export/CroissantExporterSlimTest.java b/src/test/java/edu/harvard/iq/dataverse/export/CroissantExporterSlimTest.java
index fcbc9611818..e746f67ac06 100644
--- a/src/test/java/edu/harvard/iq/dataverse/export/CroissantExporterSlimTest.java
+++ b/src/test/java/edu/harvard/iq/dataverse/export/CroissantExporterSlimTest.java
@@ -2,7 +2,10 @@
import static org.junit.jupiter.api.Assertions.*;
+import io.gdcc.spi.export.DatasetExportQuery;
import io.gdcc.spi.export.ExportDataProvider;
+import io.gdcc.spi.export.FileExportQuery;
+import io.gdcc.spi.export.PageRequest;
import jakarta.json.Json;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
@@ -23,9 +26,12 @@
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
+import java.util.stream.Stream;
+
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
+import org.w3c.dom.Document;
public class CroissantExporterSlimTest {
@@ -50,6 +56,11 @@ public static void setUp() {
outputStreamMinimal = new ByteArrayOutputStream();
dataProviderMinimal =
new ExportDataProvider() {
+ @Override
+ public JsonObject getDatasetJson(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetJson() {
String pathToJsonFile =
@@ -61,7 +72,12 @@ public JsonObject getDatasetJson() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetORE(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetORE() {
String pathToJsonFile =
@@ -73,7 +89,17 @@ public JsonObject getDatasetORE() {
return null;
}
}
-
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery, PageRequest pageRequest) {
+ return Stream.empty();
+ }
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery) {
+ return Stream.empty();
+ }
+
@Override
public JsonArray getDatasetFileDetails() {
String pathToJsonFile =
@@ -85,7 +111,12 @@ public JsonArray getDatasetFileDetails() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetSchemaDotOrg(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetSchemaDotOrg() {
String pathToJsonFile =
@@ -97,7 +128,12 @@ public JsonObject getDatasetSchemaDotOrg() {
return null;
}
}
-
+
+ @Override
+ public Document getDataCiteXml(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public String getDataCiteXml() {
try {
@@ -114,6 +150,11 @@ public String getDataCiteXml() {
outputStreamMax = new ByteArrayOutputStream();
dataProviderMax =
new ExportDataProvider() {
+ @Override
+ public JsonObject getDatasetJson(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetJson() {
String pathToJsonFile =
@@ -125,7 +166,12 @@ public JsonObject getDatasetJson() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetORE(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetORE() {
String pathToJsonFile =
@@ -137,7 +183,17 @@ public JsonObject getDatasetORE() {
return null;
}
}
-
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery, PageRequest pageRequest) {
+ return Stream.empty();
+ }
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery) {
+ return Stream.empty();
+ }
+
@Override
public JsonArray getDatasetFileDetails() {
String pathToJsonFile =
@@ -149,7 +205,12 @@ public JsonArray getDatasetFileDetails() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetSchemaDotOrg(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetSchemaDotOrg() {
String pathToJsonFile =
@@ -161,7 +222,12 @@ public JsonObject getDatasetSchemaDotOrg() {
return null;
}
}
-
+
+ @Override
+ public Document getDataCiteXml(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public String getDataCiteXml() {
try {
@@ -178,6 +244,11 @@ public String getDataCiteXml() {
outputStreamCars = new ByteArrayOutputStream();
dataProviderCars =
new ExportDataProvider() {
+ @Override
+ public JsonObject getDatasetJson(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetJson() {
String pathToJsonFile =
@@ -189,7 +260,12 @@ public JsonObject getDatasetJson() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetORE(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetORE() {
String pathToJsonFile =
@@ -201,7 +277,17 @@ public JsonObject getDatasetORE() {
return null;
}
}
-
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery, PageRequest pageRequest) {
+ return Stream.empty();
+ }
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery) {
+ return Stream.empty();
+ }
+
@Override
public JsonArray getDatasetFileDetails() {
String pathToJsonFile =
@@ -213,7 +299,12 @@ public JsonArray getDatasetFileDetails() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetSchemaDotOrg(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetSchemaDotOrg() {
String pathToJsonFile =
@@ -225,7 +316,12 @@ public JsonObject getDatasetSchemaDotOrg() {
return null;
}
}
-
+
+ @Override
+ public Document getDataCiteXml(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public String getDataCiteXml() {
try {
@@ -242,6 +338,11 @@ public String getDataCiteXml() {
outputStreamRestricted = new ByteArrayOutputStream();
dataProviderRestricted =
new ExportDataProvider() {
+ @Override
+ public JsonObject getDatasetJson(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetJson() {
String pathToJsonFile =
@@ -253,7 +354,12 @@ public JsonObject getDatasetJson() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetORE(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetORE() {
String pathToJsonFile =
@@ -265,7 +371,17 @@ public JsonObject getDatasetORE() {
return null;
}
}
-
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery, PageRequest pageRequest) {
+ return Stream.empty();
+ }
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery) {
+ return Stream.empty();
+ }
+
@Override
public JsonArray getDatasetFileDetails() {
String pathToJsonFile =
@@ -277,7 +393,12 @@ public JsonArray getDatasetFileDetails() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetSchemaDotOrg(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetSchemaDotOrg() {
String pathToJsonFile =
@@ -289,7 +410,12 @@ public JsonObject getDatasetSchemaDotOrg() {
return null;
}
}
-
+
+ @Override
+ public Document getDataCiteXml(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public String getDataCiteXml() {
try {
@@ -306,6 +432,11 @@ public String getDataCiteXml() {
outputStreamJunk = new ByteArrayOutputStream();
dataProviderJunk =
new ExportDataProvider() {
+ @Override
+ public JsonObject getDatasetJson(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetJson() {
String pathToJsonFile =
@@ -317,7 +448,12 @@ public JsonObject getDatasetJson() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetORE(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetORE() {
String pathToJsonFile =
@@ -329,7 +465,17 @@ public JsonObject getDatasetORE() {
return null;
}
}
-
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery, PageRequest pageRequest) {
+ return Stream.empty();
+ }
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery) {
+ return Stream.empty();
+ }
+
@Override
public JsonArray getDatasetFileDetails() {
String pathToJsonFile =
@@ -341,7 +487,12 @@ public JsonArray getDatasetFileDetails() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetSchemaDotOrg(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetSchemaDotOrg() {
String pathToJsonFile =
@@ -353,7 +504,12 @@ public JsonObject getDatasetSchemaDotOrg() {
return null;
}
}
-
+
+ @Override
+ public Document getDataCiteXml(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public String getDataCiteXml() {
try {
@@ -370,6 +526,11 @@ public String getDataCiteXml() {
outputStreamDraft = new ByteArrayOutputStream();
dataProviderDraft =
new ExportDataProvider() {
+ @Override
+ public JsonObject getDatasetJson(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetJson() {
String pathToJsonFile =
@@ -381,7 +542,12 @@ public JsonObject getDatasetJson() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetORE(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetORE() {
String pathToJsonFile =
@@ -393,7 +559,17 @@ public JsonObject getDatasetORE() {
return null;
}
}
-
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery, PageRequest pageRequest) {
+ return Stream.empty();
+ }
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery) {
+ return Stream.empty();
+ }
+
@Override
public JsonArray getDatasetFileDetails() {
String pathToJsonFile =
@@ -405,7 +581,12 @@ public JsonArray getDatasetFileDetails() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetSchemaDotOrg(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetSchemaDotOrg() {
String pathToJsonFile =
@@ -417,7 +598,12 @@ public JsonObject getDatasetSchemaDotOrg() {
return null;
}
}
-
+
+ @Override
+ public Document getDataCiteXml(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public String getDataCiteXml() {
try {
diff --git a/src/test/java/edu/harvard/iq/dataverse/export/CroissantExporterTest.java b/src/test/java/edu/harvard/iq/dataverse/export/CroissantExporterTest.java
index 6c6da792d4e..6ac453ac745 100644
--- a/src/test/java/edu/harvard/iq/dataverse/export/CroissantExporterTest.java
+++ b/src/test/java/edu/harvard/iq/dataverse/export/CroissantExporterTest.java
@@ -2,7 +2,10 @@
import static org.junit.jupiter.api.Assertions.*;
+import io.gdcc.spi.export.DatasetExportQuery;
import io.gdcc.spi.export.ExportDataProvider;
+import io.gdcc.spi.export.FileExportQuery;
+import io.gdcc.spi.export.PageRequest;
import jakarta.json.Json;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
@@ -23,9 +26,12 @@
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
+import java.util.stream.Stream;
+
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
+import org.w3c.dom.Document;
public class CroissantExporterTest {
@@ -50,6 +56,11 @@ public static void setUp() {
outputStreamMinimal = new ByteArrayOutputStream();
dataProviderMinimal =
new ExportDataProvider() {
+ @Override
+ public JsonObject getDatasetJson(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetJson() {
String pathToJsonFile =
@@ -61,7 +72,12 @@ public JsonObject getDatasetJson() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetORE(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetORE() {
String pathToJsonFile =
@@ -73,7 +89,17 @@ public JsonObject getDatasetORE() {
return null;
}
}
-
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery, PageRequest pageRequest) {
+ return Stream.empty();
+ }
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery) {
+ return Stream.empty();
+ }
+
@Override
public JsonArray getDatasetFileDetails() {
String pathToJsonFile =
@@ -85,7 +111,12 @@ public JsonArray getDatasetFileDetails() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetSchemaDotOrg(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetSchemaDotOrg() {
String pathToJsonFile =
@@ -97,7 +128,12 @@ public JsonObject getDatasetSchemaDotOrg() {
return null;
}
}
-
+
+ @Override
+ public Document getDataCiteXml(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public String getDataCiteXml() {
try {
@@ -114,6 +150,11 @@ public String getDataCiteXml() {
outputStreamMax = new ByteArrayOutputStream();
dataProviderMax =
new ExportDataProvider() {
+ @Override
+ public JsonObject getDatasetJson(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetJson() {
String pathToJsonFile =
@@ -125,7 +166,12 @@ public JsonObject getDatasetJson() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetORE(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetORE() {
String pathToJsonFile =
@@ -137,7 +183,17 @@ public JsonObject getDatasetORE() {
return null;
}
}
-
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery, PageRequest pageRequest) {
+ return Stream.empty();
+ }
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery) {
+ return Stream.empty();
+ }
+
@Override
public JsonArray getDatasetFileDetails() {
String pathToJsonFile =
@@ -149,7 +205,12 @@ public JsonArray getDatasetFileDetails() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetSchemaDotOrg(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetSchemaDotOrg() {
String pathToJsonFile =
@@ -161,7 +222,12 @@ public JsonObject getDatasetSchemaDotOrg() {
return null;
}
}
-
+
+ @Override
+ public Document getDataCiteXml(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public String getDataCiteXml() {
try {
@@ -178,6 +244,11 @@ public String getDataCiteXml() {
outputStreamCars = new ByteArrayOutputStream();
dataProviderCars =
new ExportDataProvider() {
+ @Override
+ public JsonObject getDatasetJson(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetJson() {
String pathToJsonFile =
@@ -189,7 +260,12 @@ public JsonObject getDatasetJson() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetORE(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetORE() {
String pathToJsonFile =
@@ -201,7 +277,17 @@ public JsonObject getDatasetORE() {
return null;
}
}
-
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery, PageRequest pageRequest) {
+ return Stream.empty();
+ }
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery) {
+ return Stream.empty();
+ }
+
@Override
public JsonArray getDatasetFileDetails() {
String pathToJsonFile =
@@ -213,7 +299,12 @@ public JsonArray getDatasetFileDetails() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetSchemaDotOrg(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetSchemaDotOrg() {
String pathToJsonFile =
@@ -225,7 +316,12 @@ public JsonObject getDatasetSchemaDotOrg() {
return null;
}
}
-
+
+ @Override
+ public Document getDataCiteXml(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public String getDataCiteXml() {
try {
@@ -242,6 +338,11 @@ public String getDataCiteXml() {
outputStreamRestricted = new ByteArrayOutputStream();
dataProviderRestricted =
new ExportDataProvider() {
+ @Override
+ public JsonObject getDatasetJson(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetJson() {
String pathToJsonFile =
@@ -253,7 +354,12 @@ public JsonObject getDatasetJson() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetORE(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetORE() {
String pathToJsonFile =
@@ -265,7 +371,17 @@ public JsonObject getDatasetORE() {
return null;
}
}
-
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery, PageRequest pageRequest) {
+ return Stream.empty();
+ }
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery) {
+ return Stream.empty();
+ }
+
@Override
public JsonArray getDatasetFileDetails() {
String pathToJsonFile =
@@ -277,7 +393,12 @@ public JsonArray getDatasetFileDetails() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetSchemaDotOrg(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetSchemaDotOrg() {
String pathToJsonFile =
@@ -289,7 +410,12 @@ public JsonObject getDatasetSchemaDotOrg() {
return null;
}
}
-
+
+ @Override
+ public Document getDataCiteXml(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public String getDataCiteXml() {
try {
@@ -306,6 +432,11 @@ public String getDataCiteXml() {
outputStreamJunk = new ByteArrayOutputStream();
dataProviderJunk =
new ExportDataProvider() {
+ @Override
+ public JsonObject getDatasetJson(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetJson() {
String pathToJsonFile =
@@ -317,7 +448,12 @@ public JsonObject getDatasetJson() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetORE(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetORE() {
String pathToJsonFile =
@@ -329,7 +465,17 @@ public JsonObject getDatasetORE() {
return null;
}
}
-
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery, PageRequest pageRequest) {
+ return Stream.empty();
+ }
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery) {
+ return Stream.empty();
+ }
+
@Override
public JsonArray getDatasetFileDetails() {
String pathToJsonFile =
@@ -341,7 +487,12 @@ public JsonArray getDatasetFileDetails() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetSchemaDotOrg(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetSchemaDotOrg() {
String pathToJsonFile =
@@ -353,7 +504,12 @@ public JsonObject getDatasetSchemaDotOrg() {
return null;
}
}
-
+
+ @Override
+ public Document getDataCiteXml(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public String getDataCiteXml() {
try {
@@ -370,6 +526,11 @@ public String getDataCiteXml() {
outputStreamDraft = new ByteArrayOutputStream();
dataProviderDraft =
new ExportDataProvider() {
+ @Override
+ public JsonObject getDatasetJson(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetJson() {
String pathToJsonFile =
@@ -381,7 +542,12 @@ public JsonObject getDatasetJson() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetORE(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetORE() {
String pathToJsonFile =
@@ -393,7 +559,17 @@ public JsonObject getDatasetORE() {
return null;
}
}
-
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery, PageRequest pageRequest) {
+ return Stream.empty();
+ }
+
+ @Override
+ public Stream getDatasetFileDetails(FileExportQuery fileExportQuery) {
+ return Stream.empty();
+ }
+
@Override
public JsonArray getDatasetFileDetails() {
String pathToJsonFile =
@@ -405,7 +581,12 @@ public JsonArray getDatasetFileDetails() {
return null;
}
}
-
+
+ @Override
+ public JsonObject getDatasetSchemaDotOrg(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public JsonObject getDatasetSchemaDotOrg() {
String pathToJsonFile =
@@ -417,7 +598,12 @@ public JsonObject getDatasetSchemaDotOrg() {
return null;
}
}
-
+
+ @Override
+ public Document getDataCiteXml(DatasetExportQuery datasetExportQuery) {
+ return null;
+ }
+
@Override
public String getDataCiteXml() {
try {