diff --git a/modules/dataverse-parent/pom.xml b/modules/dataverse-parent/pom.xml index e14e057f1e0..b2ae5bb8c43 100644 --- a/modules/dataverse-parent/pom.xml +++ b/modules/dataverse-parent/pom.xml @@ -167,6 +167,7 @@ 5.3.0 + 2.2.0-SNAPSHOT 2.0.2 @@ -425,20 +426,20 @@ - ${compilerArgument} - + + com.google.auto.service auto-service ${auto-service.version} + + + io.gdcc + dataverse-spi + ${gdcc.spi.version} + diff --git a/src/main/java/edu/harvard/iq/dataverse/api/ApiConstants.java b/src/main/java/edu/harvard/iq/dataverse/api/ApiConstants.java index 15114085c21..2bc129e58c8 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/ApiConstants.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/ApiConstants.java @@ -1,5 +1,7 @@ package edu.harvard.iq.dataverse.api; +import java.util.List; + public final class ApiConstants { private ApiConstants() { @@ -17,6 +19,10 @@ private ApiConstants() { public static final String DS_VERSION_LATEST = ":latest"; public static final String DS_VERSION_DRAFT = ":draft"; public static final String DS_VERSION_LATEST_PUBLISHED = ":latest-published"; + public static final String DS_VERSION_IDENTIFIER_REGEX = "^(:latest|:draft|:latest-published|\\d+(?:\\.\\d+)?)$"; + public static final List 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 {