Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
435f7fd
refactor(export): modularize ExportService by introducing ExporterReg…
poikilotherm Aug 7, 2026
5395ad3
refactor(export): make ExportService a @Stateless EJB bean
poikilotherm Aug 7, 2026
1c33930
feat(export): introduce new ExportCache subsystem with cache key, inv…
poikilotherm Aug 7, 2026
279db40
refactor(export): extract file embargo expiry logic from ExportServic…
poikilotherm Aug 7, 2026
38cb8a9
refactor(export): move caching logic from ExportService to new Storag…
poikilotherm Aug 7, 2026
64700c4
refactor(export): remove legacy unversioned cache logic from StorageI…
poikilotherm Aug 7, 2026
234626f
feat(export): enhance ExportCacheKey with validation and convenience …
poikilotherm Aug 7, 2026
121e617
refactor(export): relocate service and provider classes to `export.se…
poikilotherm Aug 7, 2026
b405d7d
docs(export): add Javadoc to private helpers in StorageIOCache #11405
poikilotherm Aug 18, 2026
88f90da
refactor(export): move invalidators list from ExportCacheInvalidator …
poikilotherm Aug 18, 2026
873b589
style(export): rename `exporterRegistry` field to `registry` in Expor…
poikilotherm Aug 18, 2026
6b124d7
feat(export): make ExportCache instance available in service #11405
poikilotherm Aug 18, 2026
a2ee3d1
refactor(export): make cache clearing version-aware #11405
poikilotherm Aug 18, 2026
148832c
style(export): move export trigger service methods next to each other…
poikilotherm Aug 18, 2026
b6b6ef5
feat(export): add prerequisite dependency verification to ExporterReg…
poikilotherm Aug 19, 2026
6cd4a99
feat(export): cache formatRequiredBy map in ExporterRegistryBean #11405
poikilotherm Aug 19, 2026
1e142a9
feat(export): add topological comparator to ExporterRegistryBean #11405
poikilotherm Aug 19, 2026
8f1d928
feat(util,export): enforce owner-only permissions on export temp file…
poikilotherm Aug 19, 2026
1b64d43
fix(export): decouple ExportCacheKey from JPA entities #11405
poikilotherm Aug 20, 2026
01e1e95
refactor(export): replace depth-based comparator with transitive depe…
poikilotherm Aug 20, 2026
031a6a0
refactor(export): replace Labels record with sealed Details interface…
poikilotherm Aug 21, 2026
fdac53e
style(export): convert field comments to block comment style for read…
poikilotherm Aug 21, 2026
95d08f1
refactor(export): store formatName and friendlyVersion again as Expor…
poikilotherm Aug 21, 2026
77fd70e
refactor(export): remove obsolete exporter lookup methods from Export…
poikilotherm Aug 21, 2026
c59d2be
fix(export): guard against null formatName in ExporterRegistryBean#ge…
poikilotherm Aug 21, 2026
d1403aa
feat(util): add FailureEscalation for threshold-based log level escal…
poikilotherm Aug 21, 2026
7468098
docs(export): correct legacy cache name behavior in StorageIOCache Ja…
poikilotherm Aug 21, 2026
bbe8cd9
feat(export): apply FailureEscalation to StorageIOCache log levels #1…
poikilotherm Aug 21, 2026
9aefcaa
feat(export): add ExportPipelineBean as central export orchestration …
poikilotherm Aug 21, 2026
f318f8e
refactor(export): introduce ExportPipelineBean to ExportServiceBean #…
poikilotherm Aug 21, 2026
2d12621
feat(export): add isCacheable helper centralizing version cache polic…
poikilotherm Aug 21, 2026
cb0f910
refactor(export): rework exportFormats to use pipeline and topologica…
poikilotherm Aug 21, 2026
101b9b4
refactor(export): delegate ExportService.getExport to ExportPipeline …
poikilotherm Aug 21, 2026
e2894e3
docs(export): note limitations of naive staleness invalidation #11405
poikilotherm Aug 21, 2026
1074726
refactor(commands): replace ExportService.getInstance() with CommandC…
poikilotherm Aug 21, 2026
750fff4
refactor(oai): replace ExportService.getInstance() with EJB-injected …
poikilotherm Aug 21, 2026
e29f920
refactor(api): replace ExportService.getInstance() with EJB-injected …
poikilotherm Aug 21, 2026
b6c9f46
refactor(signposting): replace ExportService.getInstance() with EJB-i…
poikilotherm Aug 21, 2026
48cdce9
refactor(ui): replace ExportService.getInstance() with EJB-injected b…
poikilotherm Aug 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/main/java/edu/harvard/iq/dataverse/DatasetPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import edu.harvard.iq.dataverse.engine.command.impl.PublishDataverseCommand;
import edu.harvard.iq.dataverse.engine.command.impl.UpdateDatasetVersionCommand;
import edu.harvard.iq.dataverse.export.ExportService;
import edu.harvard.iq.dataverse.export.service.ExporterRegistryBean;
import edu.harvard.iq.dataverse.util.cache.CacheFactoryBean;
import edu.harvard.iq.dataverse.util.json.JsonUtil;
import io.gdcc.spi.export.ExportException;
Expand Down Expand Up @@ -255,6 +256,8 @@ public enum DisplayMode {
DvObjectServiceBean dvObjectService;
@EJB
CacheFactoryBean cacheFactory;
@EJB
ExporterRegistryBean exporterRegistryService;
@Inject
DataverseRequestServiceBean dvRequestService;
@Inject
Expand Down Expand Up @@ -7000,7 +7003,7 @@ public String getSignpostingLinkHeader() {
return null;
}
if (signpostingLinkHeader == null) {
SignpostingResources sr = new SignpostingResources(systemConfig, workingVersion,
SignpostingResources sr = new SignpostingResources(systemConfig, exporterRegistryService, workingVersion,
JvmSettings.SIGNPOSTING_LEVEL1_AUTHOR_LIMIT.lookupOptional().orElse(""),
JvmSettings.SIGNPOSTING_LEVEL1_ITEM_LIMIT.lookupOptional().orElse(""));
signpostingLinkHeader = sr.getLinks();
Expand Down
20 changes: 19 additions & 1 deletion src/main/java/edu/harvard/iq/dataverse/EjbDataverseEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean;
import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinUserServiceBean;
import edu.harvard.iq.dataverse.dataverse.featured.DataverseFeaturedItemServiceBean;
import edu.harvard.iq.dataverse.export.service.ExportServiceBean;
import edu.harvard.iq.dataverse.export.service.ExporterRegistryBean;
import edu.harvard.iq.dataverse.license.LicenseServiceBean;
import edu.harvard.iq.dataverse.util.cache.CacheFactoryBean;
import edu.harvard.iq.dataverse.engine.DataverseEngine;
Expand Down Expand Up @@ -209,6 +211,12 @@ public class EjbDataverseEngine {
@EJB
CacheFactoryBean cacheFactory;

@EJB
ExportServiceBean exportService;

@EJB
ExporterRegistryBean exporterRegistry;

@Resource
EJBContext ejbCtxt;

Expand Down Expand Up @@ -664,7 +672,17 @@ public MetadataBlockServiceBean metadataBlocks() {
public DatasetTypeServiceBean datasetTypes() {
return datasetTypeService;
}


@Override
public ExportServiceBean exportService() {
return exportService;
}

@Override
public ExporterRegistryBean exporterRegistry() {
return exporterRegistry;
}

@Override
public void beginCommandSequence() {
this.commandsCalled = new Stack();
Expand Down
54 changes: 22 additions & 32 deletions src/main/java/edu/harvard/iq/dataverse/FilePage.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@
import edu.harvard.iq.dataverse.engine.command.impl.RestrictFileCommand;
import edu.harvard.iq.dataverse.engine.command.impl.UningestFileCommand;
import edu.harvard.iq.dataverse.engine.command.impl.UpdateDatasetVersionCommand;
import edu.harvard.iq.dataverse.export.ExportService;
import edu.harvard.iq.dataverse.export.service.ExportServiceBean;
import edu.harvard.iq.dataverse.export.service.ExporterRegistryBean;
import edu.harvard.iq.dataverse.export.service.ExporterRegistryBean.Details;
import io.gdcc.spi.export.ExportException;
import io.gdcc.spi.export.Exporter;
import edu.harvard.iq.dataverse.externaltools.ExternalTool;
import edu.harvard.iq.dataverse.externaltools.ExternalToolHandler;
import edu.harvard.iq.dataverse.externaltools.ExternalToolServiceBean;
Expand All @@ -35,7 +36,6 @@
import edu.harvard.iq.dataverse.makedatacount.MakeDataCountLoggingServiceBean;
import edu.harvard.iq.dataverse.makedatacount.MakeDataCountLoggingServiceBean.MakeDataCountEntry;
import edu.harvard.iq.dataverse.privateurl.PrivateUrlServiceBean;
import edu.harvard.iq.dataverse.settings.FeatureFlags;
import edu.harvard.iq.dataverse.settings.JvmSettings;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
import edu.harvard.iq.dataverse.util.BundleUtil;
Expand Down Expand Up @@ -63,7 +63,6 @@
import jakarta.faces.application.FacesMessage;
import jakarta.faces.component.UIComponent;
import jakarta.faces.context.FacesContext;
import jakarta.faces.validator.ValidatorException;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
Expand Down Expand Up @@ -128,6 +127,10 @@ public class FilePage implements java.io.Serializable {
IngestServiceBean ingestService;
@EJB
SystemConfig systemConfig;
@EJB
ExportServiceBean exportService;
@EJB
ExporterRegistryBean exporterRegistryService;


@Inject
Expand Down Expand Up @@ -463,30 +466,19 @@ public void setVersion(String version) {
this.version = version;
}

public List< String[]> getExporters(){
List<String[]> retList = new ArrayList<>();
String myHostURL = systemConfig.getDataverseSiteUrl();
for (String [] provider : ExportService.getInstance().getExportersLabels() ){
String formatName = provider[1];
String formatDisplayName = provider[0];

Exporter exporter = null;
try {
exporter = ExportService.getInstance().getExporter(formatName);
} catch (ExportException ex) {
exporter = null;
}
if (exporter != null && exporter.isAvailableToUsers()) {
// Not all metadata exports should be presented to the web users!
// Some are only for harvesting clients.

String[] temp = new String[2];
temp[0] = formatDisplayName;
temp[1] = myHostURL + "/api/datasets/export?exporter=" + formatName + "&persistentId=" + fileMetadata.getDatasetVersion().getDataset().getGlobalId().asString();
retList.add(temp);
}
}
return retList;
public List<String[]> getExporters(){
String urlTemplate = systemConfig.getDataverseSiteUrl() + "/api/datasets/export?exporter=%s&persistentId=%s";

return exporterRegistryService.getDetails().stream()
.filter(Details::isAvailableToUsers)
.map(details -> new String[]{
details.localizedDisplayName(),
urlTemplate.formatted(
details.formatName(),
fileMetadata.getDatasetVersion().getDataset().getGlobalId().asString()
)
})
.toList();
}

public String saveProvFreeform(String freeformTextInput, DataFile dataFileFromPopup) throws CommandException {
Expand Down Expand Up @@ -637,15 +629,13 @@ public String uningestFile() throws CommandException {
editDataset = file.getOwner();
if (editDataset.isReleased()) {
try {
ExportService instance = ExportService.getInstance();
instance.exportAllFormats(editDataset);

exportService.exportAllFormats(editDataset);
} catch (ExportException ex) {
// Something went wrong!
// Just like with indexing, a failure to export is not a fatal
// condition. We'll just log the error as a warning and keep
// going:
logger.log(Level.WARNING, "Uningest: Exception while exporting:{0}", ex.getMessage());
logger.log(Level.WARNING, "Uningest: Exception while exporting: {0}", ex);
}
}
datafileService.save(file);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import edu.harvard.iq.dataverse.engine.command.impl.GetLatestAccessibleDatasetVersionCommand;
import edu.harvard.iq.dataverse.engine.command.impl.GetLatestPublishedDatasetVersionCommand;
import edu.harvard.iq.dataverse.engine.command.impl.GetSpecificPublishedDatasetVersionCommand;
import edu.harvard.iq.dataverse.export.service.ExportServiceBean;
import edu.harvard.iq.dataverse.export.service.ExporterRegistryBean;
import edu.harvard.iq.dataverse.externaltools.ExternalToolServiceBean;
import edu.harvard.iq.dataverse.license.LicenseServiceBean;
import edu.harvard.iq.dataverse.makedatacount.DatasetMetricsServiceBean;
Expand Down Expand Up @@ -246,6 +248,12 @@ String getWrappedMessageWhenJson() {

@EJB
TemplateServiceBean templateSvc;

@EJB
ExportServiceBean exportSvc;

@EJB
ExporterRegistryBean exporterRegistrySvc;

@Inject
FailedPIDResolutionLoggingServiceBean fprLogService;
Expand Down
5 changes: 1 addition & 4 deletions src/main/java/edu/harvard/iq/dataverse/api/Files.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import edu.harvard.iq.dataverse.engine.command.exception.CommandException;
import edu.harvard.iq.dataverse.engine.command.exception.IllegalCommandException;
import edu.harvard.iq.dataverse.engine.command.impl.*;
import edu.harvard.iq.dataverse.export.ExportService;
import io.gdcc.spi.export.ExportException;
import edu.harvard.iq.dataverse.externaltools.ExternalTool;
import edu.harvard.iq.dataverse.externaltools.ExternalToolHandler;
Expand Down Expand Up @@ -60,7 +59,6 @@
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;

import static edu.harvard.iq.dataverse.util.json.JsonPrinter.*;
import static jakarta.ws.rs.core.Response.Status.BAD_REQUEST;
Expand Down Expand Up @@ -888,8 +886,7 @@ public Response extractNcml(@Context ContainerRequestContext crc, @Parameter(des
private void exportDatasetMetadata(SettingsServiceBean settingsServiceBean, Dataset theDataset) {

try {
ExportService instance = ExportService.getInstance();
instance.exportAllFormats(theDataset);
exportSvc.exportAllFormats(theDataset);

} catch (ExportException ex) {
// Something went wrong!
Expand Down
36 changes: 15 additions & 21 deletions src/main/java/edu/harvard/iq/dataverse/api/Info.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,17 @@

import java.util.logging.Logger;
import edu.harvard.iq.dataverse.customization.CustomizationConstants;
import edu.harvard.iq.dataverse.export.service.ExporterRegistryBean;
import edu.harvard.iq.dataverse.util.json.JsonUtil;
import jakarta.ws.rs.*;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.WebTarget;
import edu.harvard.iq.dataverse.export.ExportService;
import edu.harvard.iq.dataverse.settings.JvmSettings;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
import edu.harvard.iq.dataverse.util.SystemConfig;
import io.gdcc.spi.export.Exporter;
import io.gdcc.spi.export.ExportException;
import io.gdcc.spi.export.XMLExporter;
import jakarta.ejb.EJB;
import jakarta.json.Json;
import jakarta.json.JsonObjectBuilder;
import jakarta.json.JsonValue;
import jakarta.ws.rs.core.MediaType;
Expand Down Expand Up @@ -149,24 +146,21 @@ public Response getZipDownloadLimit() {
description = "Returns dataset export formats with display name, media type, harvestability, user-interface visibility, and XML metadata when available.")
public Response getExportFormats() {
JsonObjectBuilder responseModel = JsonUtil.createObjectBuilder();
ExportService instance = ExportService.getInstance();
for (String[] labels : instance.getExportersLabels()) {
try {
Exporter exporter = instance.getExporter(labels[1]);
JsonObjectBuilder exporterObject = JsonUtil.createObjectBuilder().add("displayName", labels[0])
.add("mediaType", exporter.getMediaType()).add("isHarvestable", exporter.isHarvestable())
.add("isVisibleInUserInterface", exporter.isAvailableToUsers());
if (exporter instanceof XMLExporter xmlExporter) {
exporterObject.add("XMLNameSpace", xmlExporter.getXMLNameSpace())
.add("XMLSchemaLocation", xmlExporter.getXMLSchemaLocation())
.add("XMLSchemaVersion", xmlExporter.getXMLSchemaVersion());
}
responseModel.add(labels[1], exporterObject);
}
catch (ExportException ex){
logger.warning("Failed to get: " + labels[1]);
logger.warning(ex.getLocalizedMessage());

for (ExporterRegistryBean.Details exporterDetail : exporterRegistrySvc.getDetails()) {
JsonObjectBuilder exporterObject = JsonUtil.createObjectBuilder()
.add("displayName", exporterDetail.localizedDisplayName())
.add("mediaType", exporterDetail.mediaType())
.add("isHarvestable", exporterDetail.isHarvestable())
.add("isVisibleInUserInterface", exporterDetail.isAvailableToUsers());

if (exporterRegistrySvc.get(exporterDetail) instanceof XMLExporter xmlExporter) {
exporterObject.add("XMLNameSpace", xmlExporter.getXMLNameSpace())
.add("XMLSchemaLocation", xmlExporter.getXMLSchemaLocation())
.add("XMLSchemaVersion", xmlExporter.getXMLSchemaVersion());
}

responseModel.add(exporterDetail.formatName(), exporterObject);
}
return ok(responseModel);
}
Expand Down
21 changes: 8 additions & 13 deletions src/main/java/edu/harvard/iq/dataverse/api/Metadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@

import edu.harvard.iq.dataverse.Dataset;
import edu.harvard.iq.dataverse.DatasetServiceBean;
import edu.harvard.iq.dataverse.export.ExportService;

import java.util.Date;
import java.util.logging.Logger;

import edu.harvard.iq.dataverse.export.service.ExporterRegistryBean;
import jakarta.ejb.EJB;
import jakarta.ws.rs.*;

Expand All @@ -23,6 +24,8 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
Expand Down Expand Up @@ -167,18 +170,10 @@ private List<String> validateFormatNames(String formats) {

List<String> formatNames = new ArrayList<>(Arrays.asList(formats.split(",")));

Set<String> supportedFormatNames = new HashSet<>();
for (String[] providerLabels : ExportService.getInstance().getExportersLabels()) {
supportedFormatNames.add(providerLabels[1]);
}

//for (String formatName : formatNames) {
// if (!supportedFormatNames.contains(formatName)) {
// throw new BadRequestException(formatName + " is not a supported format");
// }
//}
if (!supportedFormatNames.containsAll(formatNames)) {
throw new BadRequestException("Invalid/unsupported format name(s)");
try {
exporterRegistrySvc.requireAllExist(formatNames);
} catch (IllegalArgumentException ex) {
throw new BadRequestException("Invalid/unsupported format name(s)" + ex.getMessage());
}

return formatNames;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import edu.harvard.iq.dataverse.dataset.DatasetFieldsValidator;
import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinUserServiceBean;
import edu.harvard.iq.dataverse.dataverse.featured.DataverseFeaturedItemServiceBean;
import edu.harvard.iq.dataverse.export.service.ExportServiceBean;
import edu.harvard.iq.dataverse.export.service.ExporterRegistryBean;
import edu.harvard.iq.dataverse.license.LicenseServiceBean;
import edu.harvard.iq.dataverse.search.IndexServiceBean;
import edu.harvard.iq.dataverse.search.SearchService;
Expand Down Expand Up @@ -143,4 +145,8 @@ public interface CommandContext {
public DatasetFieldsValidator datasetFieldsValidator();

public LicenseServiceBean licenses();

public ExportServiceBean exportService();

public ExporterRegistryBean exporterRegistry();
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import edu.harvard.iq.dataverse.engine.command.RequiredPermissions;
import edu.harvard.iq.dataverse.engine.command.exception.CommandException;
import edu.harvard.iq.dataverse.engine.command.exception.IllegalCommandException;
import edu.harvard.iq.dataverse.export.ExportService;
import io.gdcc.spi.export.ExportException;
import edu.harvard.iq.dataverse.util.BundleUtil;
import edu.harvard.iq.dataverse.util.DatasetFieldUtil;
Expand Down Expand Up @@ -249,8 +248,7 @@ public boolean onSuccess(CommandContext ctxt, Object r) {

// And the exported metadata files
try {
ExportService instance = ExportService.getInstance();
instance.exportAllFormats(d);
ctxt.exportService().exportAllFormats(d);
} catch (ExportException ex) {
// Just like with indexing, a failure to export is not a fatal condition.
retVal = false;
Expand Down
Loading