@Override
public DatashareDataset find(Context context, UUID uuid) throws SQLException {
- // TODO Auto-generated method stub
- throw new UnsupportedOperationException("Unimplemented method 'find'");
+ return null;
}
@Override
public String getName(DatashareDataset dso) {
- // TODO Auto-generated method stub
- throw new UnsupportedOperationException("Unimplemented method 'getName'");
+ return dso != null ? dso.getName() : null;
}
@Override
@@ -368,8 +374,7 @@ public void moveMetadata(Context context, DatashareDataset dso, String schema, S
@Override
public int getSupportsTypeConstant() {
- // TODO Auto-generated method stub
- throw new UnsupportedOperationException("Unimplemented method 'getSupportsTypeConstant'");
+ return Constants.DATASHARE_DATASET;
}
@Override
@@ -380,14 +385,12 @@ public void setMetadataModified(DatashareDataset dso) {
@Override
public DatashareDataset findByIdOrLegacyId(Context context, String id) throws SQLException {
- // TODO Auto-generated method stub
- throw new UnsupportedOperationException("Unimplemented method 'findByIdOrLegacyId'");
+ return null;
}
@Override
public DatashareDataset findByLegacyId(Context context, int id) throws SQLException {
- // TODO Auto-generated method stub
- throw new UnsupportedOperationException("Unimplemented method 'findByLegacyId'");
+ return null;
}
}
diff --git a/dspace-api/src/main/java/org/dspace/core/Constants.java b/dspace-api/src/main/java/org/dspace/core/Constants.java
index 24af699cae86..27390c1817a3 100644
--- a/dspace-api/src/main/java/org/dspace/core/Constants.java
+++ b/dspace-api/src/main/java/org/dspace/core/Constants.java
@@ -64,10 +64,10 @@ public class Constants {
// DATASHARE - start
/** Type of dataset objects */
public static final int DATASHARE_DATASET = 9;
-
+
/** Type of batch import objects */
public static final int BATCH_IMPORT = 10;
-
+
/** Type of uun2email objects */
public static final int UUN_2_EMAIL = 11;
diff --git a/dspace-api/src/main/java/org/dspace/embargo/DefaultEmbargoSetter.java b/dspace-api/src/main/java/org/dspace/embargo/DefaultEmbargoSetter.java
index c6e78d91ce5b..298630b39734 100644
--- a/dspace-api/src/main/java/org/dspace/embargo/DefaultEmbargoSetter.java
+++ b/dspace-api/src/main/java/org/dspace/embargo/DefaultEmbargoSetter.java
@@ -93,10 +93,7 @@ public void setEmbargo(Context context, Item item)
String bnn = bn.getName();
if (!(bnn.equals(Constants.LICENSE_BUNDLE_NAME) || bnn.equals(Constants.METADATA_BUNDLE_NAME) || bnn
.equals(CreativeCommonsServiceImpl.CC_BUNDLE_NAME))) {
- //AuthorizeManager.removePoliciesActionFilter(context, bn, Constants.READ);
- generatePolicies(context, liftDate.toDate(), null, bn, item.getOwningCollection());
for (Bitstream bs : bn.getBitstreams()) {
- //AuthorizeManager.removePoliciesActionFilter(context, bs, Constants.READ);
generatePolicies(context, liftDate.toDate(), null, bs, item.getOwningCollection());
}
}
@@ -170,17 +167,19 @@ public void checkEmbargo(Context context, Item item)
.getPoliciesActionFilter(context, bn, Constants.READ)) {
if (rp.getStartDate() == null) {
// DATASHARE - start
- // In Datashare, we have found items where EPerson is null on the ResourcePolicy of item bitstreams.
+ // In Datashare, we have found items where EPerson is null
+ // on the ResourcePolicy of item bitstreams.
// This has caused the display of the warning message to fail with a NullPointerException.
// System.out.println("CHECK WARNING: Item " + item.getHandle() + ", Bundle " + bn
// .getName() + " allows READ by " +
// ((rp.getEPerson() != null) ? "Group " + rp.getGroup().getName() :
// "EPerson " + rp.getEPerson().getFullName()));
System.out.println("CHECK WARNING: Item " + item.getHandle() + ", Bundle " + bn
- .getName() + " allows READ by " +
- ((rp.getGroup() != null) ? "Group " + rp.getGroup().getName() : "Group not set" ) +
- ((rp.getEPerson() != null) ? "EPerson " + rp.getEPerson().getFullName() : "; EPerson not set"));
-
+ .getName() + " allows READ by "
+ + ((rp.getGroup() != null) ? "Group " + rp.getGroup().getName() : "Group not set")
+ + ((rp.getEPerson() != null) ? "EPerson " + rp.getEPerson().getFullName()
+ : "; EPerson not set"));
+
// DATASHARE - end
}
}
@@ -191,16 +190,18 @@ public void checkEmbargo(Context context, Item item)
.getPoliciesActionFilter(context, bs, Constants.READ)) {
if (rp.getStartDate() == null) {
// DATASHARE - start
- // In Datashare, we have found items where EPerson is null on the ResourcePolicy of item bitstreams.
+ // In Datashare, we have found items where EPerson is null
+ // on the ResourcePolicy of item bitstreams.
// This has caused the display of the warning message to fail with a NullPointerException.
// System.out.println("CHECK WARNING: Item " + item.getHandle() + ", Bitstream " + bs
// .getName() + " (in Bundle " + bn.getName() + ") allows READ by " +
// ((rp.getEPerson() != null) ? "Group " + rp.getGroup().getName() :
// "EPerson " + rp.getEPerson().getFullName()));
- System.out.println("CHECK WARNING: Item " + item.getHandle() + ", Bitstream " + bs
- .getName() + " (in Bundle " + bn.getName() + ") allows READ by " +
- ((rp.getGroup() != null) ? "Group " + rp.getGroup().getName() : "Group not set" ) +
- ((rp.getEPerson() != null) ? "EPerson " + rp.getEPerson().getFullName() : "; EPerson not set"));
+ System.out.println("CHECK WARNING: Item " + item.getHandle() + ", Bitstream " + bs
+ .getName() + " (in Bundle " + bn.getName() + ") allows READ by "
+ + ((rp.getGroup() != null) ? "Group " + rp.getGroup().getName() : "Group not set")
+ + ((rp.getEPerson() != null) ? "EPerson " + rp.getEPerson().getFullName()
+ : "; EPerson not set"));
// DATASHARE - end
}
}
diff --git a/dspace-api/src/main/java/org/dspace/embargo/EmbargoServiceImpl.java b/dspace-api/src/main/java/org/dspace/embargo/EmbargoServiceImpl.java
index b9b7495c3e15..bc1d28edb085 100644
--- a/dspace-api/src/main/java/org/dspace/embargo/EmbargoServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/embargo/EmbargoServiceImpl.java
@@ -16,6 +16,7 @@
import java.util.Iterator;
import java.util.List;
+import jakarta.mail.MessagingException;
import org.apache.logging.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DCDate;
@@ -31,8 +32,6 @@
import org.dspace.services.ConfigurationService;
import org.springframework.beans.factory.annotation.Autowired;
-import jakarta.mail.MessagingException;
-
/**
* Public interface to the embargo subsystem.
*
diff --git a/dspace-api/src/main/java/org/dspace/embargo/service/EmbargoService.java b/dspace-api/src/main/java/org/dspace/embargo/service/EmbargoService.java
index 95f855239115..b8e6f23a1b90 100644
--- a/dspace-api/src/main/java/org/dspace/embargo/service/EmbargoService.java
+++ b/dspace-api/src/main/java/org/dspace/embargo/service/EmbargoService.java
@@ -103,10 +103,10 @@ public void liftEmbargo(Context context, Item item)
public Iterator- findItemsByLiftMetadata(Context context) throws SQLException, IOException, AuthorizeException;
// DATASHARE - start
- /**
- * Check for any items whose embargo is about to expire.
- * @param context
- */
- public void checkForExpiry(Context context);
- // DATASHARE - end
+ /**
+ * Check for any items whose embargo is about to expire.
+ * @param context
+ */
+ public void checkForExpiry(Context context);
+ // DATASHARE - end
}
diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java
index 2ebf0501cf11..615a9cc417fd 100644
--- a/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java
+++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DOIOrganiser.java
@@ -466,7 +466,7 @@ public static void runCLI(Context context, DOIOrganiser organiser, String[] args
} catch (Exception ex) {
LOG.error("Error updating DOI identifier: {}", ex.getMessage(), ex);
System.err.println("Error updating DOI identifier: " + ex.getMessage());
- // Datshare - end
+ // Datshare - end
}
}
}
@@ -646,7 +646,7 @@ public void register(DOI doiRow)
/**
* Reserve DOI with the provider,
- *
+ *
* @param doiRow - doi to reserve
*/
public void reserve(DOI doiRow) {
diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java
index af065a277792..d502231545fe 100644
--- a/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java
+++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java
@@ -436,7 +436,7 @@ public void reserveDOI(Context context, DSpaceObject dso, String doi)
format.setEncoding("UTF-8");
XMLOutputter xout = new XMLOutputter(format);
log.info("XML for when metadataDOI and DOI don't match:\n" + xout.outputString(root));
- } catch(Exception e) {
+ } catch (Exception e) {
log.info("Cannot display XML when metadataDOI and doi don't match " + e.toString());
}
// DATASHARE - end
@@ -457,8 +457,8 @@ public void reserveDOI(Context context, DSpaceObject dso, String doi)
format.setEncoding("UTF-8");
XMLOutputter xout = new XMLOutputter(format);
log.info("XML sent to Datacite to reserve or update DOI:\n" + xout.outputString(root));
- } catch(Exception e) {
- log.info("Cannot display XML sent to Datacite " + e.toString());
+ } catch (Exception e) {
+ log.info("Cannot display XML sent to Datacite " + e.toString());
}
// DATASHARE - end
diff --git a/dspace-api/src/main/java/uk/ac/ed/datashare/commands/DatashareDoiCitationUpdaterCLI.java b/dspace-api/src/main/java/uk/ac/ed/datashare/commands/DatashareDoiCitationUpdaterCLI.java
index 5e39e58c2572..6b7ba8278ac3 100644
--- a/dspace-api/src/main/java/uk/ac/ed/datashare/commands/DatashareDoiCitationUpdaterCLI.java
+++ b/dspace-api/src/main/java/uk/ac/ed/datashare/commands/DatashareDoiCitationUpdaterCLI.java
@@ -1,3 +1,10 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
package uk.ac.ed.datashare.commands;
import java.sql.SQLException;
@@ -36,438 +43,438 @@
/**
* Functionality to register items with no DOIs and update Metadata in Datashare
* with DOI. Currently only Dublin Core dc.identifier.citation is added.
- *
+ *
* Cron:
* 5 8-19 * * * $DSPACE/bin/dspace ds-doi-citation -c > $DSPACE/log/doi-citation-updater.log 2>&1
- *
+ *
* @author John Pinto
- *
- *
+ *
+ *
*/
public class DatashareDoiCitationUpdaterCLI {
- private static final Logger log = LogManager.getLogger(DatashareDoiCitationUpdaterCLI.class);
-
- private Context context;
-
- public DatashareDoiCitationUpdaterCLI(Context context) {
- this.context = context;
- }
-
- public static void main(String[] argv) {
- // create an options object and populate it
- CommandLineParser parser = new PosixParser();
-
- Options options = new Options();
-
- options.addOption("d", "register-dois", false, "Register dois for items that have no doi");
- options.addOption("c", "create citations", false, "Create citation for items that have a newly created doi");
-
- DatashareDoiCitationUpdaterCLI du = new DatashareDoiCitationUpdaterCLI(new Context());
- HelpFormatter helpformater = new HelpFormatter();
- try {
- CommandLine line = parser.parse(options, argv);
- if (line.hasOption('d')) {
- log.info("Started Registering DOIs");
- System.out.println("Started Registering citations");
- du.registerDois();
- log.info("Completed Registering DOIs");
- System.out.println("Completed Registering citations");
- } else if (line.hasOption('c')) {
- log.info("Started Creating citations");
- System.out.println("Started Creating citations");
- du.createCitations();
- log.info("Completed Creating citations");
- System.out.println("Completed Creating citations");
- } else {
- helpformater.printHelp("\nDataShare DOI\n", options);
- }
- } catch (ParseException ex) {
- log.info(ex);
- System.out.println(ex.getMessage());
- helpformater.printHelp("\nDataShare DOI\n", options);
- }
- }
-
- private void registerDois() {
- this.context.turnOffAuthorisationSystem();
-
- try {
- DOIIdentifierProvider doiProvider = new DSpace().getSingletonService(DOIIdentifierProvider.class);
- ItemService itemService = ContentServiceFactory.getInstance().getItemService();
- ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
-
- // Convert iterator to stream and process items functionally
- StreamSupport.stream(
- Spliterators.spliteratorUnknownSize(
- itemService.findAll(context),
- Spliterator.ORDERED),
- false)
- .filter(item -> !hasEmbargo(item, itemService, configurationService))
- .forEach(item -> processItemDoi(item, doiProvider));
-
- this.context.complete();
- } catch (SQLException ex) {
- throw new RuntimeException(ex);
- } finally {
- this.context.restoreAuthSystemState();
- }
- }
-
- /**
- * Check if an item has an embargo using DSpace core APIs
- *
- * @param item The item to check
- * @param itemService The item service
- * @param configurationService The configuration service
- * @return True if the item has an embargo
- */
- private boolean hasEmbargo(Item item, ItemService itemService, ConfigurationService configurationService) {
- boolean hasEmbargo = true;
-
- try {
- // Get the embargo field from configuration (default is "dc.date.available")
- String embargoField = configurationService.getProperty("embargo.field.lift", "dc.date.available");
-
- // Parse the embargo field to get schema, element, qualifier
- String[] fieldParts = embargoField.split("\\.");
- String schema = fieldParts.length > 0 ? fieldParts[0] : "dc";
- String element = fieldParts.length > 1 ? fieldParts[1] : "date";
- String qualifier = fieldParts.length > 2 ? fieldParts[2] : "available";
-
- // Get embargo metadata
- List embargoList = itemService.getMetadata(item, schema, element, qualifier, Item.ANY,
- false);
-
- if (embargoList == null || embargoList.isEmpty()) {
- hasEmbargo = false;
- } else {
- // Check if embargo date has passed
- Date now = new Date();
- hasEmbargo = false; // Assume no embargo unless we find a future date
-
- for (MetadataValue embargoValue : embargoList) {
- try {
- // Parse the embargo date
- DCDate embargoDate = new DCDate(embargoValue.getValue());
- Date embargoDateAsDate = embargoDate.toDate();
-
- // If embargo date is in the future, item is still embargoed
- if (embargoDateAsDate != null && embargoDateAsDate.after(now)) {
- hasEmbargo = true;
- break;
- }
- } catch (Exception e) {
- // If we can't parse the date, assume it's embargoed for safety
- log.warn("Could not parse embargo date for item {}: {}", item.getID(), embargoValue.getValue());
- hasEmbargo = true;
- break;
- }
- }
- }
-
- log.info("Item {} hasEmbargo: {}", item.getID(), hasEmbargo);
-
- } catch (Exception e) {
- log.error("Error checking embargo for item {}: {}", item.getID(), e.getMessage());
- // Default to having embargo if we can't determine
- hasEmbargo = true;
- }
-
- return hasEmbargo;
- }
-
- /**
- * Process a single item to look up or register a DOI
- *
- * @param item The item to process
- * @param doiProvider The DOI provider service
- */
- private void processItemDoi(Item item, DOIIdentifierProvider doiProvider) {
- try {
- String doi = lookupDoi(item, doiProvider);
-
- if (doi == null) {
- log.info("Register doi for " + item.getID());
- try {
- doiProvider.register(context, item);
- } catch (IdentifierException ex) {
- log.error("*** Unable to register doi for " + item.getID());
- }
- } else {
- log.info("Item " + item.getID() + " has " + doi);
- }
- } catch (Exception e) {
- log.error("Error processing DOI for item " + item.getID() + ": " + e.getMessage());
- }
- }
-
- /**
- * Look up DOI for an item
- *
- * @param item The item to look up
- * @param doiProvider The DOI provider service
- * @return The DOI if found, null otherwise
- */
- private String lookupDoi(Item item, DOIIdentifierProvider doiProvider) {
- try {
- return doiProvider.lookup(this.context, item);
- } catch (IdentifierNotResolvableException | IdentifierNotFoundException ex) {
- return null;
- }
- }
-
- /**
- * Create a citation for all items that have a new doi.
- */
- private void createCitations() {
- context.turnOffAuthorisationSystem();
-
- try {
- ItemService itemService = ContentServiceFactory.getInstance().getItemService();
- // Convert iterator to stream and process items functionally
- StreamSupport.stream(
- Spliterators.spliteratorUnknownSize(
- itemService.findAll(context),
- Spliterator.ORDERED),
- false)
- .filter(item -> needsCitationUpdate(item))
- .forEach(item -> processItemCitation(item));
-
- context.complete();
- } catch (SQLException ex) {
- throw new RuntimeException(ex);
- } finally {
- context.restoreAuthSystemState();
- }
-
- }
-
-
- private boolean needsCitationUpdate(Item item) {
- ItemService itemService = ContentServiceFactory.getInstance().getItemService();
-
- // Get citation directly using DSpace API
- List citations = itemService.getMetadata(item, "dc", "identifier", "citation", Item.ANY, false);
- String citation = citations.isEmpty() ? null : citations.get(0).getValue();
-
- // Check if item has DOI directly using DSpace API
- List identifiers = itemService.getMetadata(item, "dc", "identifier", "uri", Item.ANY, false);
- boolean hasDoi = identifiers.stream()
- .anyMatch(identifier -> identifier.getValue().startsWith("https://doi.org"));
-
- log.info("Item {} citation: '{}' hasDoi: {}", item.getID(), citation, hasDoi);
-
- // Case 1: No citation
- boolean needsNewCitation = citation == null;
-
- // Case 2: Has citation but it doesn't contain the DOI URL
- boolean needsUpdatedCitation = hasDoi && citation != null && !citation.contains("https://doi.org");
-
- log.info("Item {} needsNewCitation: {} needsUpdatedCitation: {}",
- item.getID(), needsNewCitation, needsUpdatedCitation);
-
- return needsNewCitation || needsUpdatedCitation;
- }
-
- private void processItemCitation(Item item) {
- try {
- ItemService itemService = ContentServiceFactory.getInstance().getItemService();
-
- // Get current citation
- List citations = itemService.getMetadata(item, "dc", "identifier", "citation", Item.ANY,
- false);
- String citation = citations.isEmpty() ? null : citations.get(0).getValue();
-
- // Check if item has DOI
- List identifiers = itemService.getMetadata(item, "dc", "identifier", "uri", Item.ANY, false);
- boolean hasDoi = identifiers.stream()
- .anyMatch(identifier -> identifier.getValue().startsWith("https://doi.org"));
-
- log.info("Item " + item.getID() + " citation: " + citation);
-
- if (citation == null) {
- // Create new citation
- String newCitation = createCitation(item);
- if (newCitation != null) {
- itemService.addMetadata(context, item, "dc", "identifier", "citation", "en", newCitation);
- }
- } else if (citation != null && !citation.contains("https://doi.org") && hasDoi) {
- // Clear existing citation and create new one
- itemService.clearMetadata(context, item, "dc", "identifier", "citation", Item.ANY);
- String newCitation = createCitation(item);
- if (newCitation != null) {
- itemService.addMetadata(context, item, "dc", "identifier", "citation", "en", newCitation);
- log.info("Item " + item.getID() + " has new citation: " + newCitation);
- }
- }
-
- itemService.update(context, item);
- } catch (AuthorizeException | SQLException ex) {
- log.error("Error updating citation for item " + item.getID() + ": " + ex.getMessage());
- }
- }
-
- /**
- * Create a citation for a given DSpace item using DSpace core APIs
- */
- private String createCitation(Item item) {
- try {
- ItemService itemService = ContentServiceFactory.getInstance().getItemService();
- StringBuilder buffer = new StringBuilder(200);
-
- // Get creators
- List creators = itemService.getMetadata(item, "dc", "creator", Item.ANY, Item.ANY, false);
- boolean creatorGiven = !creators.isEmpty();
-
- if (creatorGiven) {
- // Add creators
- for (int i = 0; i < creators.size(); i++) {
- if (i > 0) {
- buffer.append("; ");
- }
- buffer.append(creators.get(i).getValue());
- }
- buffer.append(". ");
- } else {
- // Add publisher if no creators
- List publishers = itemService.getMetadata(item, "dc", "publisher", Item.ANY, Item.ANY,
- false);
- if (!publishers.isEmpty()) {
- buffer.append(" ");
- buffer.append(publishers.get(0).getValue());
- buffer.append(".");
- }
- buffer.append(" ");
- }
-
- // Add date available year if available
- buffer.append("(");
- List dateAvailable = itemService.getMetadata(item, "dc", "date", "available", Item.ANY,
- false);
- if (!dateAvailable.isEmpty()) {
- String dateStr = dateAvailable.get(0).getValue();
- // Extract year from date string (assuming format like "2023-01-01" or "2023")
- String year = dateStr.length() >= 4 ? dateStr.substring(0, 4) : dateStr;
- buffer.append(year);
- } else {
- // No date available, use current year
- Calendar calendar = new GregorianCalendar();
- calendar.setTime(new Date());
- buffer.append(calendar.get(Calendar.YEAR));
- }
- buffer.append("). ");
-
- // Add title
- List titles = itemService.getMetadata(item, "dc", "title", Item.ANY, Item.ANY, false);
- if (!titles.isEmpty()) {
- buffer.append(titles.get(0).getValue());
- }
- buffer.append(", ");
-
- // Add time period if available
- List temporal = itemService.getMetadata(item, "dc", "coverage", "temporal", Item.ANY, false);
- if (!temporal.isEmpty()) {
- String timePeriod = temporal.get(0).getValue();
- String[] dates = decodeTimePeriod(timePeriod);
-
- if (dates != null && dates.length == 2) {
- String from = dates[0].length() >= 4 ? dates[0].substring(0, 4) : dates[0];
- String to = dates[1].length() >= 4 ? dates[1].substring(0, 4) : dates[1];
-
- if (from.equals(to)) {
- timePeriod = from;
- } else {
- timePeriod = from + "-" + to;
- }
-
- buffer.append(timePeriod);
- buffer.append(" ");
- }
- }
-
- // Add item type
- List types = itemService.getMetadata(item, "dc", "type", Item.ANY, Item.ANY, false);
- buffer.append("[");
- if (!types.isEmpty()) {
- buffer.append(types.get(0).getValue());
- }
- buffer.append("].");
-
- // Append publisher if creator is specified
- if (creatorGiven) {
- List publishers = itemService.getMetadata(item, "dc", "publisher", Item.ANY, Item.ANY,
- false);
- if (!publishers.isEmpty()) {
- buffer.append(" ");
- buffer.append(publishers.get(0).getValue());
- buffer.append(".");
- }
- }
-
- // Add DOI if available
- List identifiers = itemService.getMetadata(item, "dc", "identifier", "uri", Item.ANY, false);
- for (MetadataValue identifier : identifiers) {
- if (identifier.getValue().startsWith("https://doi.org")) {
- buffer.append(" ");
- buffer.append(identifier.getValue());
- buffer.append(".");
- break;
- }
- }
-
- return buffer.toString();
-
- } catch (Exception e) {
- log.error("Error creating citation for item " + item.getID() + ": " + e.getMessage());
- return null;
- }
- }
-
- /**
- * Decode time period W3CDTF profile of ISO 8601.
- * (Copied from DatashareDspaceUtils since we can't use it)
- */
- private String[] decodeTimePeriod(String encoding) {
- String[] dates = null;
-
- if (encoding != null) {
- String startStr = null;
- String endStr = null;
-
- // get tokens delimited by ";"- there should be three -
- // start=, end= and scheme=
- StringTokenizer st = new StringTokenizer(encoding, ";");
-
- if (st.countTokens() > 1) {
- for (int i = 0; i < st.countTokens(); i++) {
- if (i == 0) {
- startStr = st.nextToken();
- } else if (i == 1) {
- endStr = st.nextToken();
- } else {
- break;
- }
- }
-
- String startArray[] = startStr.split("=");
- String endArray[] = endStr.split("=");
-
- if (startArray.length == 2 || endArray.length == 2) {
- dates = new String[2];
- }
-
- if (startArray.length == 2) {
- dates[0] = startArray[1];
- }
-
- if (endArray.length == 2) {
- dates[1] = endArray[1];
- }
- }
- }
-
- return dates;
- }
+ private static final Logger log = LogManager.getLogger(DatashareDoiCitationUpdaterCLI.class);
+
+ private Context context;
+
+ public DatashareDoiCitationUpdaterCLI(Context context) {
+ this.context = context;
+ }
+
+ public static void main(String[] argv) {
+ // create an options object and populate it
+ CommandLineParser parser = new PosixParser();
+
+ Options options = new Options();
+
+ options.addOption("d", "register-dois", false, "Register dois for items that have no doi");
+ options.addOption("c", "create citations", false, "Create citation for items that have a newly created doi");
+
+ DatashareDoiCitationUpdaterCLI du = new DatashareDoiCitationUpdaterCLI(new Context());
+ HelpFormatter helpformater = new HelpFormatter();
+ try {
+ CommandLine line = parser.parse(options, argv);
+ if (line.hasOption('d')) {
+ log.info("Started Registering DOIs");
+ System.out.println("Started Registering citations");
+ du.registerDois();
+ log.info("Completed Registering DOIs");
+ System.out.println("Completed Registering citations");
+ } else if (line.hasOption('c')) {
+ log.info("Started Creating citations");
+ System.out.println("Started Creating citations");
+ du.createCitations();
+ log.info("Completed Creating citations");
+ System.out.println("Completed Creating citations");
+ } else {
+ helpformater.printHelp("\nDataShare DOI\n", options);
+ }
+ } catch (ParseException ex) {
+ log.info(ex);
+ System.out.println(ex.getMessage());
+ helpformater.printHelp("\nDataShare DOI\n", options);
+ }
+ }
+
+ private void registerDois() {
+ this.context.turnOffAuthorisationSystem();
+
+ try {
+ DOIIdentifierProvider doiProvider = new DSpace().getSingletonService(DOIIdentifierProvider.class);
+ ItemService itemService = ContentServiceFactory.getInstance().getItemService();
+ ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
+
+ // Convert iterator to stream and process items functionally
+ StreamSupport.stream(
+ Spliterators.spliteratorUnknownSize(
+ itemService.findAll(context),
+ Spliterator.ORDERED),
+ false)
+ .filter(item -> !hasEmbargo(item, itemService, configurationService))
+ .forEach(item -> processItemDoi(item, doiProvider));
+
+ this.context.complete();
+ } catch (SQLException ex) {
+ throw new RuntimeException(ex);
+ } finally {
+ this.context.restoreAuthSystemState();
+ }
+ }
+
+ /**
+ * Check if an item has an embargo using DSpace core APIs
+ *
+ * @param item The item to check
+ * @param itemService The item service
+ * @param configurationService The configuration service
+ * @return True if the item has an embargo
+ */
+ private boolean hasEmbargo(Item item, ItemService itemService, ConfigurationService configurationService) {
+ boolean hasEmbargo = true;
+
+ try {
+ // Get the embargo field from configuration (default is "dc.date.available")
+ String embargoField = configurationService.getProperty("embargo.field.lift", "dc.date.available");
+
+ // Parse the embargo field to get schema, element, qualifier
+ String[] fieldParts = embargoField.split("\\.");
+ String schema = fieldParts.length > 0 ? fieldParts[0] : "dc";
+ String element = fieldParts.length > 1 ? fieldParts[1] : "date";
+ String qualifier = fieldParts.length > 2 ? fieldParts[2] : "available";
+
+ // Get embargo metadata
+ List embargoList = itemService.getMetadata(item, schema, element, qualifier, Item.ANY,
+ false);
+
+ if (embargoList == null || embargoList.isEmpty()) {
+ hasEmbargo = false;
+ } else {
+ // Check if embargo date has passed
+ Date now = new Date();
+ hasEmbargo = false; // Assume no embargo unless we find a future date
+
+ for (MetadataValue embargoValue : embargoList) {
+ try {
+ // Parse the embargo date
+ DCDate embargoDate = new DCDate(embargoValue.getValue());
+ Date embargoDateAsDate = embargoDate.toDate();
+
+ // If embargo date is in the future, item is still embargoed
+ if (embargoDateAsDate != null && embargoDateAsDate.after(now)) {
+ hasEmbargo = true;
+ break;
+ }
+ } catch (Exception e) {
+ // If we can't parse the date, assume it's embargoed for safety
+ log.warn("Could not parse embargo date for item {}: {}", item.getID(), embargoValue.getValue());
+ hasEmbargo = true;
+ break;
+ }
+ }
+ }
+
+ log.info("Item {} hasEmbargo: {}", item.getID(), hasEmbargo);
+
+ } catch (Exception e) {
+ log.error("Error checking embargo for item {}: {}", item.getID(), e.getMessage());
+ // Default to having embargo if we can't determine
+ hasEmbargo = true;
+ }
+
+ return hasEmbargo;
+ }
+
+ /**
+ * Process a single item to look up or register a DOI
+ *
+ * @param item The item to process
+ * @param doiProvider The DOI provider service
+ */
+ private void processItemDoi(Item item, DOIIdentifierProvider doiProvider) {
+ try {
+ String doi = lookupDoi(item, doiProvider);
+
+ if (doi == null) {
+ log.info("Register doi for " + item.getID());
+ try {
+ doiProvider.register(context, item);
+ } catch (IdentifierException ex) {
+ log.error("*** Unable to register doi for " + item.getID());
+ }
+ } else {
+ log.info("Item " + item.getID() + " has " + doi);
+ }
+ } catch (Exception e) {
+ log.error("Error processing DOI for item " + item.getID() + ": " + e.getMessage());
+ }
+ }
+
+ /**
+ * Look up DOI for an item
+ *
+ * @param item The item to look up
+ * @param doiProvider The DOI provider service
+ * @return The DOI if found, null otherwise
+ */
+ private String lookupDoi(Item item, DOIIdentifierProvider doiProvider) {
+ try {
+ return doiProvider.lookup(this.context, item);
+ } catch (IdentifierNotResolvableException | IdentifierNotFoundException ex) {
+ return null;
+ }
+ }
+
+ /**
+ * Create a citation for all items that have a new doi.
+ */
+ private void createCitations() {
+ context.turnOffAuthorisationSystem();
+
+ try {
+ ItemService itemService = ContentServiceFactory.getInstance().getItemService();
+ // Convert iterator to stream and process items functionally
+ StreamSupport.stream(
+ Spliterators.spliteratorUnknownSize(
+ itemService.findAll(context),
+ Spliterator.ORDERED),
+ false)
+ .filter(item -> needsCitationUpdate(item))
+ .forEach(item -> processItemCitation(item));
+
+ context.complete();
+ } catch (SQLException ex) {
+ throw new RuntimeException(ex);
+ } finally {
+ context.restoreAuthSystemState();
+ }
+
+ }
+
+
+ private boolean needsCitationUpdate(Item item) {
+ ItemService itemService = ContentServiceFactory.getInstance().getItemService();
+
+ // Get citation directly using DSpace API
+ List citations = itemService.getMetadata(item, "dc", "identifier", "citation", Item.ANY, false);
+ String citation = citations.isEmpty() ? null : citations.get(0).getValue();
+
+ // Check if item has DOI directly using DSpace API
+ List identifiers = itemService.getMetadata(item, "dc", "identifier", "uri", Item.ANY, false);
+ boolean hasDoi = identifiers.stream()
+ .anyMatch(identifier -> identifier.getValue().startsWith("https://doi.org"));
+
+ log.info("Item {} citation: '{}' hasDoi: {}", item.getID(), citation, hasDoi);
+
+ // Case 1: No citation
+ boolean needsNewCitation = citation == null;
+
+ // Case 2: Has citation but it doesn't contain the DOI URL
+ boolean needsUpdatedCitation = hasDoi && citation != null && !citation.contains("https://doi.org");
+
+ log.info("Item {} needsNewCitation: {} needsUpdatedCitation: {}",
+ item.getID(), needsNewCitation, needsUpdatedCitation);
+
+ return needsNewCitation || needsUpdatedCitation;
+ }
+
+ private void processItemCitation(Item item) {
+ try {
+ ItemService itemService = ContentServiceFactory.getInstance().getItemService();
+
+ // Get current citation
+ List citations = itemService.getMetadata(item, "dc", "identifier", "citation", Item.ANY,
+ false);
+ String citation = citations.isEmpty() ? null : citations.get(0).getValue();
+
+ // Check if item has DOI
+ List identifiers = itemService.getMetadata(item, "dc", "identifier", "uri", Item.ANY, false);
+ boolean hasDoi = identifiers.stream()
+ .anyMatch(identifier -> identifier.getValue().startsWith("https://doi.org"));
+
+ log.info("Item " + item.getID() + " citation: " + citation);
+
+ if (citation == null) {
+ // Create new citation
+ String newCitation = createCitation(item);
+ if (newCitation != null) {
+ itemService.addMetadata(context, item, "dc", "identifier", "citation", "en", newCitation);
+ }
+ } else if (citation != null && !citation.contains("https://doi.org") && hasDoi) {
+ // Clear existing citation and create new one
+ itemService.clearMetadata(context, item, "dc", "identifier", "citation", Item.ANY);
+ String newCitation = createCitation(item);
+ if (newCitation != null) {
+ itemService.addMetadata(context, item, "dc", "identifier", "citation", "en", newCitation);
+ log.info("Item " + item.getID() + " has new citation: " + newCitation);
+ }
+ }
+
+ itemService.update(context, item);
+ } catch (AuthorizeException | SQLException ex) {
+ log.error("Error updating citation for item " + item.getID() + ": " + ex.getMessage());
+ }
+ }
+
+ /**
+ * Create a citation for a given DSpace item using DSpace core APIs
+ */
+ private String createCitation(Item item) {
+ try {
+ ItemService itemService = ContentServiceFactory.getInstance().getItemService();
+ StringBuilder buffer = new StringBuilder(200);
+
+ // Get creators
+ List creators = itemService.getMetadata(item, "dc", "creator", Item.ANY, Item.ANY, false);
+ boolean creatorGiven = !creators.isEmpty();
+
+ if (creatorGiven) {
+ // Add creators
+ for (int i = 0; i < creators.size(); i++) {
+ if (i > 0) {
+ buffer.append("; ");
+ }
+ buffer.append(creators.get(i).getValue());
+ }
+ buffer.append(". ");
+ } else {
+ // Add publisher if no creators
+ List publishers = itemService.getMetadata(item, "dc", "publisher", Item.ANY, Item.ANY,
+ false);
+ if (!publishers.isEmpty()) {
+ buffer.append(" ");
+ buffer.append(publishers.get(0).getValue());
+ buffer.append(".");
+ }
+ buffer.append(" ");
+ }
+
+ // Add date available year if available
+ buffer.append("(");
+ List dateAvailable = itemService.getMetadata(item, "dc", "date", "available", Item.ANY,
+ false);
+ if (!dateAvailable.isEmpty()) {
+ String dateStr = dateAvailable.get(0).getValue();
+ // Extract year from date string (assuming format like "2023-01-01" or "2023")
+ String year = dateStr.length() >= 4 ? dateStr.substring(0, 4) : dateStr;
+ buffer.append(year);
+ } else {
+ // No date available, use current year
+ Calendar calendar = new GregorianCalendar();
+ calendar.setTime(new Date());
+ buffer.append(calendar.get(Calendar.YEAR));
+ }
+ buffer.append("). ");
+
+ // Add title
+ List titles = itemService.getMetadata(item, "dc", "title", Item.ANY, Item.ANY, false);
+ if (!titles.isEmpty()) {
+ buffer.append(titles.get(0).getValue());
+ }
+ buffer.append(", ");
+
+ // Add time period if available
+ List temporal = itemService.getMetadata(item, "dc", "coverage", "temporal", Item.ANY, false);
+ if (!temporal.isEmpty()) {
+ String timePeriod = temporal.get(0).getValue();
+ String[] dates = decodeTimePeriod(timePeriod);
+
+ if (dates != null && dates.length == 2) {
+ String from = dates[0].length() >= 4 ? dates[0].substring(0, 4) : dates[0];
+ String to = dates[1].length() >= 4 ? dates[1].substring(0, 4) : dates[1];
+
+ if (from.equals(to)) {
+ timePeriod = from;
+ } else {
+ timePeriod = from + "-" + to;
+ }
+
+ buffer.append(timePeriod);
+ buffer.append(" ");
+ }
+ }
+
+ // Add item type
+ List types = itemService.getMetadata(item, "dc", "type", Item.ANY, Item.ANY, false);
+ buffer.append("[");
+ if (!types.isEmpty()) {
+ buffer.append(types.get(0).getValue());
+ }
+ buffer.append("].");
+
+ // Append publisher if creator is specified
+ if (creatorGiven) {
+ List publishers = itemService.getMetadata(item, "dc", "publisher", Item.ANY, Item.ANY,
+ false);
+ if (!publishers.isEmpty()) {
+ buffer.append(" ");
+ buffer.append(publishers.get(0).getValue());
+ buffer.append(".");
+ }
+ }
+
+ // Add DOI if available
+ List identifiers = itemService.getMetadata(item, "dc", "identifier", "uri", Item.ANY, false);
+ for (MetadataValue identifier : identifiers) {
+ if (identifier.getValue().startsWith("https://doi.org")) {
+ buffer.append(" ");
+ buffer.append(identifier.getValue());
+ buffer.append(".");
+ break;
+ }
+ }
+
+ return buffer.toString();
+
+ } catch (Exception e) {
+ log.error("Error creating citation for item " + item.getID() + ": " + e.getMessage());
+ return null;
+ }
+ }
+
+ /**
+ * Decode time period W3CDTF profile of ISO 8601.
+ * (Copied from DatashareDspaceUtils since we can't use it)
+ */
+ private String[] decodeTimePeriod(String encoding) {
+ String[] dates = null;
+
+ if (encoding != null) {
+ String startStr = null;
+ String endStr = null;
+
+ // get tokens delimited by ";"- there should be three -
+ // start=, end= and scheme=
+ StringTokenizer st = new StringTokenizer(encoding, ";");
+
+ if (st.countTokens() > 1) {
+ for (int i = 0; i < st.countTokens(); i++) {
+ if (i == 0) {
+ startStr = st.nextToken();
+ } else if (i == 1) {
+ endStr = st.nextToken();
+ } else {
+ break;
+ }
+ }
+
+ String startArray[] = startStr.split("=");
+ String endArray[] = endStr.split("=");
+
+ if (startArray.length == 2 || endArray.length == 2) {
+ dates = new String[2];
+ }
+
+ if (startArray.length == 2) {
+ dates[0] = startArray[1];
+ }
+
+ if (endArray.length == 2) {
+ dates[1] = endArray[1];
+ }
+ }
+ }
+
+ return dates;
+ }
}
diff --git a/dspace-api/src/main/java/uk/ac/ed/datashare/event/DatashareConsumer.java b/dspace-api/src/main/java/uk/ac/ed/datashare/event/DatashareConsumer.java
index 69b2d5d389c0..1c5ba00c8132 100644
--- a/dspace-api/src/main/java/uk/ac/ed/datashare/event/DatashareConsumer.java
+++ b/dspace-api/src/main/java/uk/ac/ed/datashare/event/DatashareConsumer.java
@@ -1,3 +1,10 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
package uk.ac.ed.datashare.event;
@@ -23,61 +30,61 @@ public class DatashareConsumer implements Consumer {
*/
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(DatashareConsumer.class);
- private ItemService itemService =ContentServiceFactory.getInstance().getItemService();
+ private ItemService itemService = ContentServiceFactory.getInstance().getItemService();
private DatashareEvent datashareEvent;
@Override
- public void initialize() throws Exception {
+ public void initialize() throws Exception {
}
@Override
public void consume(Context ctx, Event event) throws Exception {
if (this.datashareEvent == null && event.getSubjectType() == Constants.COLLECTION) {
- switch (event.getEventType()) {
- case Event.ADD: {
- DSpaceObject dso = event.getObject(ctx);
- if (dso instanceof Item) {
- Item item = (Item) dso;
- if (item.isArchived()) {
- // if a new item has been created and archived,
- // mark item for cleaning up
- this.datashareEvent = new DatashareEvent(item, event.getEventType());
- }
- }
- break;
- }
- case Event.REMOVE: {
- this.datashareEvent = new DatashareEvent(
- // event detail is the item handle
- event.getDetail(), event.getEventType());
- break;
- }
- default: {
- log.info("Unkown subject type: " + event.getSubjectType());
- }
- }
- }
+ switch (event.getEventType()) {
+ case Event.ADD: {
+ DSpaceObject dso = event.getObject(ctx);
+ if (dso instanceof Item) {
+ Item item = (Item) dso;
+ if (item.isArchived()) {
+ // if a new item has been created and archived,
+ // mark item for cleaning up
+ this.datashareEvent = new DatashareEvent(item, event.getEventType());
+ }
+ }
+ break;
+ }
+ case Event.REMOVE: {
+ this.datashareEvent = new DatashareEvent(
+ // event detail is the item handle
+ event.getDetail(), event.getEventType());
+ break;
+ }
+ default: {
+ log.info("Unkown subject type: " + event.getSubjectType());
+ }
+ }
+ }
}
@Override
public void end(Context ctx) throws Exception {
if (this.datashareEvent != null) {
- switch (this.datashareEvent.getType()) {
- case Event.ADD: {
- this.addItem(ctx, this.datashareEvent.getItem());
- break;
- }
- case Event.REMOVE: {
- // new ItemDataset(ctx, this.datashareEvent.getHandle()).delete();
- break;
- }
- default: {
- log.info("DatashareConsumer: Unknown event type: " + this.datashareEvent.getType());
- }
- }
- this.datashareEvent = null;
- }
+ switch (this.datashareEvent.getType()) {
+ case Event.ADD: {
+ this.addItem(ctx, this.datashareEvent.getItem());
+ break;
+ }
+ case Event.REMOVE: {
+ // new ItemDataset(ctx, this.datashareEvent.getHandle()).delete();
+ break;
+ }
+ default: {
+ log.info("DatashareConsumer: Unknown event type: " + this.datashareEvent.getType());
+ }
+ }
+ this.datashareEvent = null;
+ }
}
@Override
@@ -87,36 +94,36 @@ public void finish(Context ctx) throws Exception {
}
- private void addItem(Context ctx, Item item) throws Exception {
- try {
- // // clear field used to store license type
- // DSpaceUtils.clearUserLicenseType(context, item);
+ private void addItem(Context ctx, Item item) throws Exception {
+ try {
+ // // clear field used to store license type
+ // DSpaceUtils.clearUserLicenseType(context, item);
- // // copy hijacked spatial country to dc.coverage.spatial
- // List vals = DSpaceUtils.getHijackedSpatial(item);
- // for (int i = 0; i < vals.size(); i++) {
- // MetaDataUtil.setSpatial(context, item, vals.get(i).getValue(), false);
- // }
+ // // copy hijacked spatial country to dc.coverage.spatial
+ // List vals = DSpaceUtils.getHijackedSpatial(item);
+ // for (int i = 0; i < vals.size(); i++) {
+ // MetaDataUtil.setSpatial(context, item, vals.get(i).getValue(), false);
+ // }
- // // clear hijacked spatial field
- // DSpaceUtils.clearHijackedSpatial(context, item);
+ // // clear hijacked spatial field
+ // DSpaceUtils.clearHijackedSpatial(context, item);
- log.info("DatashareConsumer: create dataset");
+ log.info("DatashareConsumer: create dataset");
- // create zip file
- // new ItemDataset(item).createDataset();
+ // create zip file
+ // new ItemDataset(item).createDataset();
- // ctx.turnOffAuthorisationSystem();
+ // ctx.turnOffAuthorisationSystem();
- // // commit changes
- // itemService.update(context, item);
- } catch (Exception ex) {
- throw new RuntimeException(ex);
+ // // commit changes
+ // itemService.update(context, item);
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
}
- // } catch (AuthorizeException ex) {
- // throw new RuntimeException(ex);
- // } finally {
- // context.restoreAuthSystemState();
- // }
- }
+ // } catch (AuthorizeException ex) {
+ // throw new RuntimeException(ex);
+ // } finally {
+ // context.restoreAuthSystemState();
+ // }
+ }
}
diff --git a/dspace-api/src/main/java/uk/ac/ed/datashare/event/DatashareEvent.java b/dspace-api/src/main/java/uk/ac/ed/datashare/event/DatashareEvent.java
index 9e5a5ca35238..884793b763b9 100644
--- a/dspace-api/src/main/java/uk/ac/ed/datashare/event/DatashareEvent.java
+++ b/dspace-api/src/main/java/uk/ac/ed/datashare/event/DatashareEvent.java
@@ -1,3 +1,10 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
package uk.ac.ed.datashare.event;
import org.dspace.content.Item;
diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V8.3_2026.04.12__create_dataset_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V8.3_2026.04.12__create_dataset_table.sql
new file mode 100644
index 000000000000..a40a47743a70
--- /dev/null
+++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V8.3_2026.04.12__create_dataset_table.sql
@@ -0,0 +1,23 @@
+--
+-- The contents of this file are subject to the license and copyright
+-- detailed in the LICENSE and NOTICE files at the root of the source
+-- tree and available online at
+--
+-- http://www.dspace.org/license/
+--
+
+-----------------------------------------------------------------------------------
+-- CREATE dataset table for DataShare DatashareDataset entity
+-----------------------------------------------------------------------------------
+
+CREATE TABLE IF NOT EXISTS dataset (
+ uuid UUID NOT NULL,
+ id INTEGER,
+ item_id UUID NOT NULL,
+ file_name VARCHAR(255),
+ checksum VARCHAR(255),
+ checksum_algorithm VARCHAR(255),
+ CONSTRAINT dataset_pkey PRIMARY KEY (uuid),
+ CONSTRAINT dataset_uuid_fkey FOREIGN KEY (uuid) REFERENCES dspaceobject(uuid),
+ CONSTRAINT dataset_item_id_fkey FOREIGN KEY (item_id) REFERENCES item(uuid)
+);
diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V8.0_2025.07.11__metadata-inserts_for_datashare-coverage-dates.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V8.0_2025.07.11__metadata-inserts_for_datashare-coverage-dates.sql
new file mode 100644
index 000000000000..f9bf1e6619b2
--- /dev/null
+++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V8.0_2025.07.11__metadata-inserts_for_datashare-coverage-dates.sql
@@ -0,0 +1,24 @@
+--
+-- The contents of this file are subject to the license and copyright
+-- detailed in the LICENSE and NOTICE files at the root of the source
+-- tree and available online at
+--
+-- http://www.dspace.org/license/
+--
+
+-----------------------------------------------------------------------------------------------------------------------------------
+
+-- Datashare: register dc.coverage.startDate and dc.coverage.endDate metadata fields
+-- used by DatashareSpatialAndTemporalStep to capture temporal coverage dates.
+
+-----------------------------------------------------------------------------------------------------------------------------------
+
+-- Insert dc.coverage.startDate
+INSERT INTO metadatafieldregistry (metadata_schema_id, element, qualifier)
+ SELECT (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='dc'), 'coverage', 'startDate'
+ WHERE NOT EXISTS (SELECT metadata_field_id FROM metadatafieldregistry WHERE element = 'coverage' AND qualifier='startDate' AND metadata_schema_id = (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='dc'));
+
+-- Insert dc.coverage.endDate
+INSERT INTO metadatafieldregistry (metadata_schema_id, element, qualifier)
+ SELECT (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='dc'), 'coverage', 'endDate'
+ WHERE NOT EXISTS (SELECT metadata_field_id FROM metadatafieldregistry WHERE element = 'coverage' AND qualifier='endDate' AND metadata_schema_id = (SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id='dc'));
diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V8.0_2025.07.20__drop-datashare-ds-schema.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V8.0_2025.07.20__drop-datashare-ds-schema.sql
new file mode 100644
index 000000000000..52829a0869f2
--- /dev/null
+++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V8.0_2025.07.20__drop-datashare-ds-schema.sql
@@ -0,0 +1,36 @@
+--
+-- The contents of this file are subject to the license and copyright
+-- detailed in the LICENSE and NOTICE files at the root of the source
+-- tree and available online at
+--
+-- http://www.dspace.org/license/
+--
+
+-----------------------------------------------------------------------
+-- Drop the 'ds' metadata schema that was used as an intermediate layer
+-- during Datashare form submission. All Datashare steps now write
+-- directly to standard dc.* fields, so ds.* is no longer needed.
+--
+-- This migration:
+-- 1. Deletes any metadata VALUES stored under ds.* fields
+-- 2. Deletes the ds.* field definitions from metadatafieldregistry
+-- 3. Deletes the ds schema from metadataschemaregistry
+-----------------------------------------------------------------------
+
+-- Step 1: Delete metadata values referencing ds.* fields
+DELETE FROM metadatavalue
+WHERE metadata_field_id IN (
+ SELECT mfr.metadata_field_id
+ FROM metadatafieldregistry mfr
+ JOIN metadataschemaregistry msr ON mfr.metadata_schema_id = msr.metadata_schema_id
+ WHERE msr.short_id = 'ds'
+);
+
+-- Step 2: Delete ds.* field definitions
+DELETE FROM metadatafieldregistry
+WHERE metadata_schema_id = (
+ SELECT metadata_schema_id FROM metadataschemaregistry WHERE short_id = 'ds'
+);
+
+-- Step 3: Delete the ds schema itself
+DELETE FROM metadataschemaregistry WHERE short_id = 'ds';
diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V8.3_2026.04.12__create_dataset_table.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V8.3_2026.04.12__create_dataset_table.sql
new file mode 100644
index 000000000000..a40a47743a70
--- /dev/null
+++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V8.3_2026.04.12__create_dataset_table.sql
@@ -0,0 +1,23 @@
+--
+-- The contents of this file are subject to the license and copyright
+-- detailed in the LICENSE and NOTICE files at the root of the source
+-- tree and available online at
+--
+-- http://www.dspace.org/license/
+--
+
+-----------------------------------------------------------------------------------
+-- CREATE dataset table for DataShare DatashareDataset entity
+-----------------------------------------------------------------------------------
+
+CREATE TABLE IF NOT EXISTS dataset (
+ uuid UUID NOT NULL,
+ id INTEGER,
+ item_id UUID NOT NULL,
+ file_name VARCHAR(255),
+ checksum VARCHAR(255),
+ checksum_algorithm VARCHAR(255),
+ CONSTRAINT dataset_pkey PRIMARY KEY (uuid),
+ CONSTRAINT dataset_uuid_fkey FOREIGN KEY (uuid) REFERENCES dspaceobject(uuid),
+ CONSTRAINT dataset_item_id_fkey FOREIGN KEY (item_id) REFERENCES item(uuid)
+);
diff --git a/dspace-api/src/test/java/org/dspace/app/client/DSpaceHttpClientFactoryTest.java b/dspace-api/src/test/java/org/dspace/app/client/DSpaceHttpClientFactoryTest.java
index b518f19ff4d3..94a77035c570 100644
--- a/dspace-api/src/test/java/org/dspace/app/client/DSpaceHttpClientFactoryTest.java
+++ b/dspace-api/src/test/java/org/dspace/app/client/DSpaceHttpClientFactoryTest.java
@@ -107,7 +107,9 @@ public void testBuildWithProxyConfiguredAndHostToIgnoreSet() throws Exception {
@Test
public void testBuildWithProxyConfiguredAndHostPrefixToIgnoreSet() throws Exception {
- setHttpProxyOnConfigurationService("local*", "www.test.com");
+ String hostName = mockServer.getHostName();
+ String prefix = hostName.substring(0, hostName.length() - 1) + "*";
+ setHttpProxyOnConfigurationService(prefix, "www.test.com");
CloseableHttpClient httpClient = httpClientFactory.build();
assertThat(mockProxy.getRequestCount(), is(0));
assertThat(mockServer.getRequestCount(), is(0));
@@ -122,7 +124,9 @@ public void testBuildWithProxyConfiguredAndHostPrefixToIgnoreSet() throws Except
@Test
public void testBuildWithProxyConfiguredAndHostSuffixToIgnoreSet() throws Exception {
- setHttpProxyOnConfigurationService("www.test.com", "*host");
+ String hostName = mockServer.getHostName();
+ String suffix = "*" + hostName.substring(1);
+ setHttpProxyOnConfigurationService("www.test.com", suffix);
CloseableHttpClient httpClient = httpClientFactory.build();
assertThat(mockProxy.getRequestCount(), is(0));
assertThat(mockServer.getRequestCount(), is(0));
diff --git a/dspace-api/src/test/java/org/dspace/content/datashare/DatashareItemDatasetTest.java b/dspace-api/src/test/java/org/dspace/content/datashare/DatashareItemDatasetTest.java
index 5893a6f50cb9..12c4b127003c 100644
--- a/dspace-api/src/test/java/org/dspace/content/datashare/DatashareItemDatasetTest.java
+++ b/dspace-api/src/test/java/org/dspace/content/datashare/DatashareItemDatasetTest.java
@@ -1,3 +1,10 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
package org.dspace.content.datashare;
import static org.junit.Assert.assertEquals;
diff --git a/dspace-api/src/test/java/org/dspace/statistics/export/ITIrusExportUsageEventListener.java b/dspace-api/src/test/java/org/dspace/statistics/export/ITIrusExportUsageEventListener.java
index 61e60abdbdb9..994525a99f73 100644
--- a/dspace-api/src/test/java/org/dspace/statistics/export/ITIrusExportUsageEventListener.java
+++ b/dspace-api/src/test/java/org/dspace/statistics/export/ITIrusExportUsageEventListener.java
@@ -58,8 +58,8 @@
import org.dspace.usage.UsageEvent;
import org.junit.After;
import org.junit.Before;
-import org.junit.Test;
import org.junit.Ignore;
+import org.junit.Test;
/**
* Test class for the IrusExportUsageEventListener
diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/datashare/DatashareDatasetRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/datashare/DatashareDatasetRestController.java
index a1a55df241bd..c6016e2d1f2d 100644
--- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/datashare/DatashareDatasetRestController.java
+++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/datashare/DatashareDatasetRestController.java
@@ -1,7 +1,16 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
package org.dspace.app.rest.datashare;
import java.util.UUID;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
import org.dspace.app.rest.utils.ContextUtil;
import org.dspace.content.Item;
import org.dspace.content.datashare.service.DatashareDatasetService;
@@ -13,9 +22,6 @@
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-
@RestController
@RequestMapping("/api/datashare")
public class DatashareDatasetRestController {
diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AccessStatusRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AccessStatusRest.java
index 85993f9a9213..8561f2ee9f4b 100644
--- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AccessStatusRest.java
+++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AccessStatusRest.java
@@ -18,6 +18,7 @@ public class AccessStatusRest implements RestModel {
public static final String PLURAL_NAME = NAME;
String status;
+ String embargoDate;
@Override
@JsonProperty(access = Access.READ_ONLY)
@@ -48,4 +49,12 @@ public String getStatus() {
public void setStatus(String status) {
this.status = status;
}
+
+ public String getEmbargoDate() {
+ return embargoDate;
+ }
+
+ public void setEmbargoDate(String embargoDate) {
+ this.embargoDate = embargoDate;
+ }
}
diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamRest.java
index d456f7222308..d7f7d326ad60 100644
--- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamRest.java
+++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamRest.java
@@ -18,7 +18,8 @@
@LinksRest(links = {
@LinkRest(name = BitstreamRest.BUNDLE, method = "getBundle"),
@LinkRest(name = BitstreamRest.FORMAT, method = "getFormat"),
- @LinkRest(name = BitstreamRest.THUMBNAIL, method = "getThumbnail")
+ @LinkRest(name = BitstreamRest.THUMBNAIL, method = "getThumbnail"),
+ @LinkRest(name = BitstreamRest.ACCESS_STATUS, method = "getAccessStatus")
})
public class BitstreamRest extends DSpaceObjectRest {
public static final String PLURAL_NAME = "bitstreams";
@@ -28,6 +29,7 @@ public class BitstreamRest extends DSpaceObjectRest {
public static final String BUNDLE = "bundle";
public static final String FORMAT = "format";
public static final String THUMBNAIL = "thumbnail";
+ public static final String ACCESS_STATUS = "accessStatus";
private String bundleName;
diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamAccessStatusLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamAccessStatusLinkRepository.java
new file mode 100644
index 000000000000..f9306117e063
--- /dev/null
+++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamAccessStatusLinkRepository.java
@@ -0,0 +1,63 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+
+package org.dspace.app.rest.repository;
+
+import java.sql.SQLException;
+import java.util.UUID;
+
+import jakarta.annotation.Nullable;
+import jakarta.servlet.http.HttpServletRequest;
+import org.dspace.access.status.service.AccessStatusService;
+import org.dspace.app.rest.model.AccessStatusRest;
+import org.dspace.app.rest.model.BitstreamRest;
+import org.dspace.app.rest.projection.Projection;
+import org.dspace.content.Bitstream;
+import org.dspace.content.service.BitstreamService;
+import org.dspace.core.Context;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.rest.webmvc.ResourceNotFoundException;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+
+/**
+ * Link repository for calculating the access status of a Bitstream.
+ */
+@Component(BitstreamRest.CATEGORY + "." + BitstreamRest.PLURAL_NAME + "." + BitstreamRest.ACCESS_STATUS)
+public class BitstreamAccessStatusLinkRepository extends AbstractDSpaceRestRepository
+ implements LinkRestRepository {
+
+ @Autowired
+ BitstreamService bitstreamService;
+
+ @Autowired
+ AccessStatusService accessStatusService;
+
+ @PreAuthorize("permitAll()")
+ public AccessStatusRest getAccessStatus(@Nullable HttpServletRequest request,
+ UUID bitstreamId,
+ @Nullable Pageable optionalPageable,
+ Projection projection) {
+ try {
+ Context context = obtainContext();
+ Bitstream bitstream = bitstreamService.find(context, bitstreamId);
+ if (bitstream == null) {
+ throw new ResourceNotFoundException("No such bitstream: " + bitstreamId);
+ }
+ AccessStatusRest accessStatusRest = new AccessStatusRest();
+ String accessStatus = accessStatusService.getAccessStatusFromBitstream(context, bitstream);
+ accessStatusRest.setStatus(accessStatus);
+ String embargoDate = accessStatusService.getEmbargoFromBitstream(context, bitstream);
+ accessStatusRest.setEmbargoDate(embargoDate);
+ return accessStatusRest;
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemAccessStatusLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemAccessStatusLinkRepository.java
index 975171fba3c3..fa1fe3f834d3 100644
--- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemAccessStatusLinkRepository.java
+++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemAccessStatusLinkRepository.java
@@ -53,6 +53,8 @@ public AccessStatusRest getAccessStatus(@Nullable HttpServletRequest request,
AccessStatusRest accessStatusRest = new AccessStatusRest();
String accessStatus = accessStatusService.getAccessStatus(context, item);
accessStatusRest.setStatus(accessStatus);
+ String embargoDate = accessStatusService.getEmbargoFromItem(context, item);
+ accessStatusRest.setEmbargoDate(embargoDate);
return accessStatusRest;
} catch (SQLException e) {
throw new RuntimeException(e);
diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java
index 12e2551552d3..96ca1515c8b0 100644
--- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java
+++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/StatelessAuthenticationFilter.java
@@ -19,8 +19,6 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.app.rest.utils.ContextUtil;
-import org.dspace.authenticate.factory.AuthenticateServiceFactory;
-import org.dspace.authenticate.service.AuthenticationService;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.factory.AuthorizeServiceFactory;
import org.dspace.authorize.service.AuthorizeService;
diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/datashare/DatashareFunderStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/datashare/DatashareFunderStep.java
index 72a09896566c..21bcaea5fd9a 100644
--- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/datashare/DatashareFunderStep.java
+++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/datashare/DatashareFunderStep.java
@@ -7,10 +7,8 @@
*/
package org.dspace.app.rest.submit.step.datashare;
-import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
-import java.util.stream.Stream;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
@@ -30,12 +28,9 @@
import org.dspace.app.util.DCInputsReaderException;
import org.dspace.app.util.SubmissionStepConfig;
import org.dspace.content.InProgressSubmission;
-import org.dspace.content.MetadataField;
import org.dspace.content.MetadataValue;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.ItemService;
-import org.dspace.content.service.MetadataFieldService;
-import org.dspace.content.service.MetadataValueService;
import org.dspace.core.Context;
import org.dspace.core.Utils;
import org.dspace.services.ConfigurationService;
@@ -67,10 +62,6 @@ public class DatashareFunderStep extends AbstractProcessingStep {
private ItemService itemService = ContentServiceFactory.getInstance().getItemService();
- private MetadataFieldService metadataFieldService = ContentServiceFactory.getInstance().getMetadataFieldService();
-
- private MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService();
-
public DatashareFunderStep() throws DCInputsReaderException {
inputReader = new DCInputsReader();
}
@@ -190,116 +181,6 @@ public void doPatchProcessing(Context context, HttpServletRequest currentRequest
+ inputConfig.getFormName());
}
}
-
- if ("remove".equals(op.getOp()) || "add".equals(op.getOp()) || "replace".equals(op.getOp())) {
- List metadataValues = source.getItem().getMetadata();
-
- MetadataField dcContributorOtherMetadataField = metadataFieldService.findByElement(context, "dc",
- "contributor", "other");
- MetadataField dsFunderDropdownValueField = metadataFieldService.findByElement(context, "ds", "funder",
- "dropdown-value");
- MetadataField dsFunderTextField = metadataFieldService.findByElement(context, "ds", "funder",
- "text-value");
-
- // Create arrays to hold the values
- List dcContributorOtherMetadataValues = new ArrayList();
- List dcContributorOtherValues = new ArrayList();
- List dsFunderDropdownValues = new ArrayList();
- List dsFunderTextValues = new ArrayList();
-
- // Loop through all metadata values and populate the arrays
- for (MetadataValue mv : metadataValues) {
- String dcContributorOtherValue = null;
- String dsFunderDropdownValue = null;
- String dsFunderTextValue = null;
-
- log.info("mv.getMetadataField().getID(): " + mv.getMetadataField().getID());
-
- if (dcContributorOtherMetadataField != null
- && mv.getMetadataField().getID().equals(dcContributorOtherMetadataField.getID())) {
- dcContributorOtherMetadataValues.add(mv);
- dcContributorOtherValue = mv.getValue();
- dcContributorOtherValues.add(dcContributorOtherValue);
- log.info("dcContributorOtherValue: " + dcContributorOtherValue);
- } else if (dsFunderDropdownValueField != null
- && mv.getMetadataField().getID().equals(dsFunderDropdownValueField.getID())) {
- dsFunderDropdownValue = mv.getValue();
- dsFunderDropdownValues.add(dsFunderDropdownValue);
- log.info("dsFunderDropdownValue: " + dsFunderDropdownValue);
- } else if (dsFunderTextField != null
- && mv.getMetadataField().getID().equals(dsFunderTextField.getID())) {
- dsFunderTextValue = mv.getValue();
- dsFunderTextValues.add(dsFunderTextValue);
- log.info("dsFunderTextMetadataValue: " + dsFunderTextValue);
- }
- }
-
- // Update the metadata values for dcContributorOtherMetadataField, adding or
- // deleting as appropriate.
- Stream dsFunderStream = Stream.concat(
- dsFunderDropdownValues.stream(),
- dsFunderTextValues.stream());
-
- if (dcContributorOtherValues.isEmpty()) {
-
- dsFunderStream.forEach(funder -> {
- try {
- MetadataValue dcContributorOtherMetadataValue = metadataValueService.create(context,
- source.getItem(),
- dcContributorOtherMetadataField);
-
- dcContributorOtherMetadataValue.setValue(funder);
- metadataValueService.update(context, dcContributorOtherMetadataValue);
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- });
-
- } else {
- // Add any new values to the dcContributorOtherMetadataField
- dsFunderStream.filter(dsfunder -> !dcContributorOtherValues.contains(dsfunder))
- .forEach(funder -> {
- try {
- MetadataValue dcContributorOtherMetadataValue = metadataValueService.create(context,
- source.getItem(),
- dcContributorOtherMetadataField);
- dcContributorOtherMetadataValue.setValue(funder);
- metadataValueService.update(context, dcContributorOtherMetadataValue);
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- });
- // Remove any existing values from the dcContributorOtherMetadataField not in
- // dsFunderDropdownValues or dsFunderTextValues.
- dcContributorOtherValues.stream()
- .filter(dcContributorOtherValue -> !dsFunderDropdownValues.contains(dcContributorOtherValue))
- .filter(dcContributorOtherValue -> !dsFunderTextValues.contains(dcContributorOtherValue))
- .flatMap(dcContributorOtherValue -> dcContributorOtherMetadataValues.stream()
- .filter(dcContributorOtherMetadataValue -> dcContributorOtherMetadataValue.getValue()
- .equals(dcContributorOtherValue)))
- .forEach(dcContributorOtherMetadataValue -> {
- try {
- deleteItemMetadataValue(context, source, dcContributorOtherMetadataValue);
- } catch (SQLException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- });
-
- }
- }
- }
-
- private void deleteItemMetadataValue(Context context, InProgressSubmission source, MetadataValue mv)
- throws SQLException {
- // Remove metadata value association before deletion
- List itemMetadata = source.getItem().getMetadata();
- itemMetadata.remove(mv);
- source.getItem().setMetadata(itemMetadata);
- // Delete the metadata value
- metadataValueService.delete(context, mv);
}
private List getInputFieldsName(DCInputSet inputConfig, String configId) throws DCInputsReaderException {
diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/datashare/DatashareLicenseStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/datashare/DatashareLicenseStep.java
index 87e1ff8cd769..a40d94367d25 100644
--- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/datashare/DatashareLicenseStep.java
+++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/datashare/DatashareLicenseStep.java
@@ -9,7 +9,6 @@
import java.io.File;
import java.io.FileInputStream;
-import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
@@ -31,12 +30,9 @@
import org.dspace.app.util.DCInputsReaderException;
import org.dspace.app.util.SubmissionStepConfig;
import org.dspace.content.InProgressSubmission;
-import org.dspace.content.MetadataField;
import org.dspace.content.MetadataValue;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.ItemService;
-import org.dspace.content.service.MetadataFieldService;
-import org.dspace.content.service.MetadataValueService;
import org.dspace.core.Context;
import org.dspace.core.Utils;
import org.dspace.license.factory.LicenseServiceFactory;
@@ -73,10 +69,6 @@ public class DatashareLicenseStep extends AbstractProcessingStep {
private ItemService itemService = ContentServiceFactory.getInstance().getItemService();
- private MetadataFieldService metadataFieldService = ContentServiceFactory.getInstance().getMetadataFieldService();
-
- private MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService();
-
public DatashareLicenseStep() throws DCInputsReaderException {
inputReader = new DCInputsReader();
@@ -202,91 +194,24 @@ public void doPatchProcessing(Context context, HttpServletRequest currentRequest
}
if ("remove".equals(op.getOp()) || "add".equals(op.getOp()) || "replace".equals(op.getOp())) {
- List metadataValues = source.getItem().getMetadata();
-
- MetadataValue dcRightsMetadataValue = null;
- MetadataValue dsLicenseDropdownValueMetadataValue = null;
- MetadataValue dsRightsTextMetadataValue = null;
- MetadataField dcRightsMetadataField = metadataFieldService.findByElement(context, "dc", "rights", "");
- MetadataField dsLicenseDropdownValueField = metadataFieldService.findByElement(context, "ds", "license",
- "dropdown-value");
- MetadataField dsLicenseRightsTextField = metadataFieldService.findByElement(context, "ds", "license",
- "rights-text");
- for (MetadataValue mv : metadataValues) {
- log.info("mv.getMetadataField().getID(): " + mv.getMetadataField().getID());
-
- if (dcRightsMetadataField != null && mv.getMetadataField().getID().equals(dcRightsMetadataField.getID())) {
- dcRightsMetadataValue = mv;
- log.info("dcRightsMetadataValue: " + dcRightsMetadataValue.getValue());
- } else if (dsLicenseDropdownValueField != null
- && mv.getMetadataField().getID().equals(dsLicenseDropdownValueField.getID())) {
- dsLicenseDropdownValueMetadataValue = mv;
- log.info("dsLicenseDropdownValueMetadataValue: " + dsLicenseDropdownValueMetadataValue.getValue());
- } else if (dsLicenseRightsTextField != null
- && mv.getMetadataField().getID().equals(dsLicenseRightsTextField.getID())) {
- dsRightsTextMetadataValue = mv;
- log.info("dsRightsTextMetadataValue: " + dsRightsTextMetadataValue.getValue());
- }
- }
- log.info("dcRightsMetadataValue: " + dcRightsMetadataValue);
- log.info("dsRightsTextMetadataValue: " + dsRightsTextMetadataValue);
- log.info("dsLicenseDropdownValueMetadataValue: " + dsLicenseDropdownValueMetadataValue);
-
- MetadataField metadataField = metadataFieldService.findByElement(context, "dc", "rights", "");
- if (dcRightsMetadataValue == null) {
- dcRightsMetadataValue = metadataValueService.create(context, source.getItem(), metadataField);
- }
- if (dsLicenseDropdownValueMetadataValue != null
- && !dsLicenseDropdownValueMetadataValue.getValue().equals("Other")) {
- dcRightsMetadataValue.setValue(dsLicenseDropdownValueMetadataValue.getValue());
- metadataValueService.update(context, dcRightsMetadataValue);
- if (dsRightsTextMetadataValue != null) {
- deleteItemMetadataValue(context, source, dsRightsTextMetadataValue);
- }
- }
- if (dsLicenseDropdownValueMetadataValue != null
- && dsLicenseDropdownValueMetadataValue.getValue().equals("Other")) {
- String rightsText = dsRightsTextMetadataValue == null
- || StringUtils.isBlank(dsRightsTextMetadataValue.getValue()) ? ""
- : dsRightsTextMetadataValue.getValue();
+ // Check dc.rights value and manage CC license bundle accordingly
+ List rightsValues = itemService.getMetadataByMetadataString(source.getItem(), "dc.rights");
+ String dcRightsValue = (rightsValues != null && !rightsValues.isEmpty())
+ ? rightsValues.get(0).getValue() : null;
- dcRightsMetadataValue.setValue(rightsText);
- metadataValueService.update(context, dcRightsMetadataValue);
- }
-
- if (dsLicenseDropdownValueMetadataValue == null) {
-
- if (dsRightsTextMetadataValue != null) {
- deleteItemMetadataValue(context, source, dsRightsTextMetadataValue);
- }
- if (dcRightsMetadataValue != null) {
- deleteItemMetadataValue(context, source, dcRightsMetadataValue);
- }
- }
-
- if (dcRightsMetadataValue != null && dcRightsMetadataValue.getValue()
+ if (dcRightsValue != null && dcRightsValue
.equals("Creative Commons Attribution 4.0 International Public License")) {
setCCLicense(context, source);
} else {
removeCCLicense(context, source);
}
-
}
}
- private void deleteItemMetadataValue(Context context, InProgressSubmission source, MetadataValue mv)
- throws SQLException {
- // Remove metadata value association before deletion
- List itemMetadata = source.getItem().getMetadata();
- itemMetadata.remove(mv);
- source.getItem().setMetadata(itemMetadata);
- // Delete the metadata value
- metadataValueService.delete(context, mv);
- }
-
private void setCCLicense(Context context, InProgressSubmission source) {
try {
- creativeCommonsService.setLicense(context, source.getItem(), new FileInputStream(CREATIVE_COMMONS_BY_LICENCE_FILE),
+ creativeCommonsService.setLicense(context, source.getItem(),
+ new FileInputStream(CREATIVE_COMMONS_BY_LICENCE_FILE),
"text/plain");
} catch (Exception e) {
log.error(e.getMessage(), e);
diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/datashare/DatashareSpatialAndTemporalStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/datashare/DatashareSpatialAndTemporalStep.java
index ce370378e684..4245f0f287fe 100644
--- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/datashare/DatashareSpatialAndTemporalStep.java
+++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/datashare/DatashareSpatialAndTemporalStep.java
@@ -9,7 +9,6 @@
import java.io.File;
import java.io.FileInputStream;
-import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
@@ -31,12 +30,10 @@
import org.dspace.app.util.DCInputsReaderException;
import org.dspace.app.util.SubmissionStepConfig;
import org.dspace.content.InProgressSubmission;
-import org.dspace.content.MetadataField;
+import org.dspace.content.Item;
import org.dspace.content.MetadataValue;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.ItemService;
-import org.dspace.content.service.MetadataFieldService;
-import org.dspace.content.service.MetadataValueService;
import org.dspace.core.Context;
import org.dspace.core.Utils;
import org.dspace.license.factory.LicenseServiceFactory;
@@ -60,7 +57,8 @@
*/
public class DatashareSpatialAndTemporalStep extends AbstractProcessingStep {
- private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(DatashareSpatialAndTemporalStep.class);
+ private static final Logger log =
+ org.apache.logging.log4j.LogManager.getLogger(DatashareSpatialAndTemporalStep.class);
// Input reader for form configuration
private DCInputsReader inputReader;
@@ -73,10 +71,6 @@ public class DatashareSpatialAndTemporalStep extends AbstractProcessingStep {
private ItemService itemService = ContentServiceFactory.getInstance().getItemService();
- private MetadataFieldService metadataFieldService = ContentServiceFactory.getInstance().getMetadataFieldService();
-
- private MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService();
-
public DatashareSpatialAndTemporalStep() throws DCInputsReaderException {
inputReader = new DCInputsReader();
}
@@ -91,12 +85,53 @@ public DataDescribe getData(SubmissionService submissionService, InProgressSubmi
try {
DCInputSet inputConfig = inputReader.getInputsByFormName(config.getId());
readField(obj, config, data, inputConfig);
+ populateTimePeriodFromTemporal(obj, data);
} catch (DCInputsReaderException e) {
log.error(e.getMessage(), e);
}
return data;
}
+ /**
+ * If dc.coverage.temporal exists but dc.coverage.startDate / dc.coverage.endDate are absent,
+ * decode the temporal value and populate the form data so that the Angular form shows the dates.
+ */
+ private void populateTimePeriodFromTemporal(InProgressSubmission obj, DataDescribe data) {
+ boolean hasStartDate = data.getMetadata().containsKey("dc.coverage.startDate");
+ boolean hasEndDate = data.getMetadata().containsKey("dc.coverage.endDate");
+
+ if (hasStartDate && hasEndDate) {
+ return;
+ }
+
+ List temporalValues = itemService.getMetadataByMetadataString(
+ obj.getItem(), "dc.coverage.temporal");
+ if (temporalValues == null || temporalValues.isEmpty()) {
+ return;
+ }
+
+ String[] decoded = decodeTimePeriod(temporalValues.get(0).getValue());
+ if (decoded == null) {
+ return;
+ }
+
+ if (!hasStartDate && decoded[0] != null && !decoded[0].isEmpty()) {
+ MetadataValueRest startDto = new MetadataValueRest();
+ startDto.setValue(decoded[0]);
+ List startList = new ArrayList<>();
+ startList.add(startDto);
+ data.getMetadata().put("dc.coverage.startDate", startList);
+ }
+
+ if (!hasEndDate && decoded[1] != null && !decoded[1].isEmpty()) {
+ MetadataValueRest endDto = new MetadataValueRest();
+ endDto.setValue(decoded[1]);
+ List endList = new ArrayList<>();
+ endList.add(endDto);
+ data.getMetadata().put("dc.coverage.endDate", endList);
+ }
+ }
+
private void readField(InProgressSubmission obj, SubmissionStepConfig config, DataDescribe data,
DCInputSet inputConfig) throws DCInputsReaderException {
String documentTypeValue = "";
@@ -174,6 +209,12 @@ private void readField(InProgressSubmission obj, SubmissionStepConfig config, Da
@Override
public void doPatchProcessing(Context context, HttpServletRequest currentRequest, InProgressSubmission source,
Operation op, SubmissionStepConfig stepConf) throws Exception {
+
+ // Hydrate: if dc.coverage.temporal is stored but the individual date fields are absent,
+ // decode temporal back into dc.coverage.startDate / dc.coverage.endDate so that
+ // PATCH replace/remove operations can find the metadata values on the item.
+ hydrateTimePeriodFields(context, source);
+
String[] pathParts = op.getPath().substring(1).split("/");
DCInputSet inputConfig = inputReader.getInputsByFormName(stepConf.getId());
if ("remove".equals(op.getOp()) && pathParts.length < 3) {
@@ -201,68 +242,81 @@ public void doPatchProcessing(Context context, HttpServletRequest currentRequest
}
if ("remove".equals(op.getOp()) || "add".equals(op.getOp()) || "replace".equals(op.getOp())) {
- List metadataValues = source.getItem().getMetadata();
-
- MetadataValue dcCoverageTemporalMetadataValue = null;
- MetadataValue dsTimePeriodStartDateValueMetadataValue = null;
- MetadataValue dsTimePeriodEndDateMetadataValue = null;
- MetadataField dcCoverageTemporalMetadataField = metadataFieldService.findByElement(context, "dc",
- "coverage", "temporal");
- MetadataField dsTimePeriodStartDateValueField = metadataFieldService.findByElement(context, "ds",
- "timeperiod",
- "start-date");
- MetadataField dsTimePeriodEndDateField = metadataFieldService.findByElement(context, "ds", "timeperiod",
- "end-date");
- for (MetadataValue mv : metadataValues) {
- log.info("mv.getMetadataField().getID(): " + mv.getMetadataField().getID());
-
- if (dcCoverageTemporalMetadataField != null
- && mv.getMetadataField().getID().equals(dcCoverageTemporalMetadataField.getID())) {
- dcCoverageTemporalMetadataValue = mv;
- log.info("dcCoverageTemporalMetadataValue: " + dcCoverageTemporalMetadataValue.getValue());
- } else if (dsTimePeriodStartDateValueField != null
- && mv.getMetadataField().getID().equals(dsTimePeriodStartDateValueField.getID())) {
- dsTimePeriodStartDateValueMetadataValue = mv;
- log.info("dsTimePeriodStartDateValueMetadataValue: "
- + dsTimePeriodStartDateValueMetadataValue.getValue());
- } else if (dsTimePeriodEndDateField != null
- && mv.getMetadataField().getID().equals(dsTimePeriodEndDateField.getID())) {
- dsTimePeriodEndDateMetadataValue = mv;
- log.info("dsTimePeriodEndDateMetadataValue: " + dsTimePeriodEndDateMetadataValue.getValue());
- }
- }
- log.info("dcCoverageTemporalMetadataValue: " + dcCoverageTemporalMetadataValue);
- log.info("dsTimePeriodEndDateMetadataValue: " + dsTimePeriodEndDateMetadataValue);
- log.info("dsTimePeriodStartDateValueMetadataValue: " + dsTimePeriodStartDateValueMetadataValue);
+ List startDates = itemService.getMetadataByMetadataString(
+ source.getItem(), "dc.coverage.startDate");
+ List endDates = itemService.getMetadataByMetadataString(
+ source.getItem(), "dc.coverage.endDate");
+
+ if (!startDates.isEmpty() && !endDates.isEmpty()) {
+ String encodedTimePeriod = encodeTimePeriod(
+ startDates.get(0).getValue(), endDates.get(0).getValue());
+ log.info("encodedTimePeriod: " + encodedTimePeriod);
- if (dcCoverageTemporalMetadataValue == null) {
- dcCoverageTemporalMetadataValue = metadataValueService.create(context, source.getItem(),
- dcCoverageTemporalMetadataField);
- }
+ // Use itemService to ensure the item's in-memory metadata list stays in sync
+ itemService.clearMetadata(context, source.getItem(),
+ "dc", "coverage", "temporal", Item.ANY);
+ itemService.addMetadata(context, source.getItem(),
+ "dc", "coverage", "temporal", null, encodedTimePeriod);
- if (dsTimePeriodStartDateValueMetadataValue != null && dsTimePeriodEndDateMetadataValue != null) {
- String encodedTimePeriod = encodeTimePeriod(dsTimePeriodStartDateValueMetadataValue.getValue(),
- dsTimePeriodEndDateMetadataValue.getValue());
- log.info("encodedTimePeriod: " + encodedTimePeriod);
- dcCoverageTemporalMetadataValue.setValue(encodedTimePeriod);
- metadataValueService.update(context, dcCoverageTemporalMetadataValue);
+ itemService.clearMetadata(context, source.getItem(),
+ "dc", "coverage", "startDate", Item.ANY);
+ itemService.clearMetadata(context, source.getItem(),
+ "dc", "coverage", "endDate", Item.ANY);
+ } else {
+ // Incomplete date pair — remove any stale temporal encoding
+ itemService.clearMetadata(context, source.getItem(),
+ "dc", "coverage", "temporal", Item.ANY);
}
+ }
+ }
- // Remove the metadata values if the start or end date are not present
- if (dsTimePeriodStartDateValueMetadataValue == null || dsTimePeriodEndDateMetadataValue == null) {
- deleteItemMetadataValue(context, source, dcCoverageTemporalMetadataValue);
- }
+ /**
+ * If dc.coverage.temporal is stored but dc.coverage.startDate / dc.coverage.endDate are absent,
+ * decode the temporal value and recreate the individual date fields on the item.
+ * This allows subsequent PATCH replace/remove operations to find the metadata values.
+ */
+ private void hydrateTimePeriodFields(Context context, InProgressSubmission source) throws Exception {
+ List startDates = itemService.getMetadataByMetadataString(
+ source.getItem(), "dc.coverage.startDate");
+ List endDates = itemService.getMetadataByMetadataString(
+ source.getItem(), "dc.coverage.endDate");
+
+ if (!startDates.isEmpty() && !endDates.isEmpty()) {
+ return; // Both date fields already exist — nothing to hydrate
+ }
+
+ List temporalValues = itemService.getMetadataByMetadataString(
+ source.getItem(), "dc.coverage.temporal");
+ if (temporalValues.isEmpty()) {
+ return; // No temporal to decode
+ }
+
+ String[] decoded = decodeTimePeriod(temporalValues.get(0).getValue());
+ if (decoded == null) {
+ return;
+ }
+
+ // Remove temporal, recreate individual date fields
+ itemService.clearMetadata(context, source.getItem(), "dc", "coverage", "temporal", Item.ANY);
+
+ if (decoded[0] != null && !decoded[0].isEmpty()) {
+ itemService.addMetadata(context, source.getItem(),
+ "dc", "coverage", "startDate", null, decoded[0]);
+ }
+ if (decoded[1] != null && !decoded[1].isEmpty()) {
+ itemService.addMetadata(context, source.getItem(),
+ "dc", "coverage", "endDate", null, decoded[1]);
}
}
/**
* Encode a start and end date into W3CDTF profile of ISO 8601.
- *
+ *
* @param start Start date.
* @param end End date.
* @return W3CDTF profile of ISO 8601.
*/
- private String encodeTimePeriod(String start, String end) {
+ static String encodeTimePeriod(String start, String end) {
String ENCODING_SCHEME = "W3C-DTF";
String startStr = "";
String endString = "";
@@ -286,14 +340,31 @@ private String encodeTimePeriod(String start, String end) {
return buf.toString();
}
- private void deleteItemMetadataValue(Context context, InProgressSubmission source, MetadataValue mv)
- throws SQLException {
- // Remove metadata value association before deletion
- List itemMetadata = source.getItem().getMetadata();
- itemMetadata.remove(mv);
- source.getItem().setMetadata(itemMetadata);
- // Delete the metadata value
- metadataValueService.delete(context, mv);
+ /**
+ * Decode a W3CDTF encoded time period string into start and end date components.
+ *
+ * @param temporal Encoded string, e.g. "start=2021; end=2025; scheme=W3C-DTF".
+ * @return String array [startDate, endDate], or null if input is null/empty.
+ */
+ static String[] decodeTimePeriod(String temporal) {
+ if (temporal == null || temporal.isEmpty()) {
+ return null;
+ }
+
+ String startDate = null;
+ String endDate = null;
+
+ String[] tokens = temporal.split(";");
+ for (String token : tokens) {
+ String trimmed = token.trim();
+ if (trimmed.startsWith("start=")) {
+ startDate = trimmed.substring("start=".length());
+ } else if (trimmed.startsWith("end=")) {
+ endDate = trimmed.substring("end=".length());
+ }
+ }
+
+ return new String[]{startDate, endDate};
}
private void setCCLicense(Context context, InProgressSubmission source) {
diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/test-discovery.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/test-discovery.xml
index dd78bffda337..5d7325466e88 100644
--- a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/test-discovery.xml
+++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/test-discovery.xml
@@ -1292,5 +1292,140 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (search.resourcetype:Item AND latestVersion:true) OR search.resourcetype:Collection OR search.resourcetype:Community
+ -withdrawn:true AND -discoverable:false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dc.title
+ dc.contributor.author
+ dc.creator
+ dc.subject
+
+
+
+
+
+
+
+
+
+
diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java
index 1ed1e23260f9..0c8026321b27 100644
--- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java
+++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java
@@ -66,8 +66,8 @@ public void findAll() throws Exception {
//We expect the content type to be "application/hal+json;charset=UTF-8"
.andExpect(content().contentType(contentType))
- //Our default Discovery config has 5 browse indexes, so we expect this to be reflected in the page
- // object
+ //Our default Discovery config has 5 browse indexes (DATASHARE adds dateaccessioned and
+ // subject_classification, removes dateissued and srsc), so we expect this in the page object
.andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", is(5)))
.andExpect(jsonPath("$.page.totalPages", is(1)))
@@ -78,11 +78,12 @@ public void findAll() throws Exception {
//Check that all (and only) the default browse indexes are present
.andExpect(jsonPath("$._embedded.browses", containsInAnyOrder(
- BrowseIndexMatcher.dateIssuedBrowseIndex("asc"),
+ // DATASHARE - dateaccessioned and subject_classification instead of dateissued and srsc
+ BrowseIndexMatcher.dateAccessionedBrowseIndex("asc"),
BrowseIndexMatcher.contributorBrowseIndex("asc"),
BrowseIndexMatcher.titleBrowseIndex("asc"),
BrowseIndexMatcher.subjectBrowseIndex("asc"),
- BrowseIndexMatcher.hierarchicalBrowseIndex("srsc")
+ BrowseIndexMatcher.subjectClassificationBrowseIndex("asc")
)))
;
}
@@ -103,15 +104,10 @@ public void findBrowseByTitle() throws Exception {
@Test
public void findBrowseByDateIssued() throws Exception {
- //When we call the root endpoint
+ // DATASHARE - dateissued browse index is not configured in DataShare,
+ // so the endpoint returns 404
getClient().perform(get("/api/discover/browses/dateissued"))
- //The status has to be 200 OK
- .andExpect(status().isOk())
- //We expect the content type to be "application/hal+json;charset=UTF-8"
- .andExpect(content().contentType(contentType))
-
- //Check that the JSON root matches the expected browse index
- .andExpect(jsonPath("$", BrowseIndexMatcher.dateIssuedBrowseIndex("asc")))
+ .andExpect(status().isNotFound())
;
}
@@ -131,16 +127,10 @@ public void findBrowseByContributor() throws Exception {
@Test
public void findBrowseByVocabulary() throws Exception {
- //Use srsc as this vocabulary is included by default
- //When we call the root endpoint
+ // DATASHARE - srsc vocabulary is disabled via webui.browse.vocabularies.disabled
+ // so the srsc browse endpoint returns 404
getClient().perform(get("/api/discover/browses/srsc"))
- //The status has to be 200 OK
- .andExpect(status().isOk())
- //We expect the content type to be "application/hal+json;charset=UTF-8"
- .andExpect(content().contentType(contentType))
-
- //Check that the JSON root matches the expected browse index
- .andExpect(jsonPath("$", BrowseIndexMatcher.hierarchicalBrowseIndex("srsc")))
+ .andExpect(status().isNotFound())
;
}
@@ -1309,9 +1299,10 @@ public void testPaginationBrowseByDateIssuedItems() throws Exception {
context.restoreAuthSystemState();
//** WHEN **
- //An anonymous user browses the items in the Browse by date issued endpoint
- //sorted ascending by tile with a page size of 5
- getClient().perform(get("/api/discover/browses/dateissued/items")
+ //An anonymous user browses the items in the Browse by title endpoint
+ //sorted ascending by title with a page size of 5
+ // DATASHARE - changed from dateissued to title browse (no dateissued browse in DataShare)
+ getClient().perform(get("/api/discover/browses/title/items")
.param("sort", "title,asc")
.param("size", "5"))
@@ -1342,7 +1333,7 @@ public void testPaginationBrowseByDateIssuedItems() throws Exception {
)));
//The next page gives us the last two items
- getClient().perform(get("/api/discover/browses/dateissued/items")
+ getClient().perform(get("/api/discover/browses/title/items")
.param("sort", "title,asc")
.param("size", "5")
.param("page", "1"))
@@ -1368,7 +1359,7 @@ public void testPaginationBrowseByDateIssuedItems() throws Exception {
String adminToken = getAuthToken(admin.getEmail(), password);
//The next page gives us the last two items
- getClient(adminToken).perform(get("/api/discover/browses/dateissued/items")
+ getClient(adminToken).perform(get("/api/discover/browses/title/items")
.param("sort", "title,asc")
.param("size", "5")
.param("page", "1"))
@@ -1459,9 +1450,10 @@ public void testPaginationBrowseByDateIssuedItemsWithScope() throws Exception {
context.restoreAuthSystemState();
//** WHEN **
- //An anonymous user browses the items in the Browse by date issued endpoint
- //sorted ascending by tile with a page size of 5
- getClient().perform(get("/api/discover/browses/dateissued/items")
+ //An anonymous user browses the items in the Browse by title endpoint
+ //sorted ascending by title with a page size of 5
+ // DATASHARE - changed from dateissued to title browse (no dateissued browse in DataShare)
+ getClient().perform(get("/api/discover/browses/title/items")
.param("scope", String.valueOf(col2.getID()))
.param("sort", "title,asc")
.param("size", "5"))
@@ -1490,7 +1482,7 @@ public void testPaginationBrowseByDateIssuedItemsWithScope() throws Exception {
)));
String adminToken = getAuthToken(admin.getEmail(), password);
- getClient(adminToken).perform(get("/api/discover/browses/dateissued/items")
+ getClient(adminToken).perform(get("/api/discover/browses/title/items")
.param("scope", String.valueOf(col2.getID()))
.param("sort", "title,asc")
.param("size", "5"))
@@ -1845,8 +1837,10 @@ public void testBrowseByItemsStartsWith() throws Exception {
.withSubject("Science Fiction")
.build();
+ // DATASHARE - renamed from "Python" to "Javanese" to share title prefix with "Java"
+ // for title browse startsWith testing (no dateissued browse in DataShare)
Item item3 = ItemBuilder.createItem(context, col1)
- .withTitle("Python")
+ .withTitle("Javanese")
.withAuthor("Van Rossum, Guido")
.withIssueDate("1990")
.withSubject("Computing")
@@ -1884,9 +1878,10 @@ public void testBrowseByItemsStartsWith() throws Exception {
// ---- BROWSES BY ITEM ----
//** WHEN **
- //An anonymous user browses the items in the Browse by date issued endpoint
- //with startsWith set to 199
- getClient().perform(get("/api/discover/browses/dateissued/items?startsWith=199")
+ //An anonymous user browses the items in the Browse by title endpoint
+ //with startsWith set to Java
+ // DATASHARE - changed from dateissued to title browse (no dateissued browse in DataShare)
+ getClient().perform(get("/api/discover/browses/title/items?startsWith=Java")
.param("size", "2"))
//** THEN **
@@ -1901,12 +1896,12 @@ public void testBrowseByItemsStartsWith() throws Exception {
.andExpect(jsonPath("$.page.number", is(0)))
.andExpect(jsonPath("$.page.size", is(2)))
- //Verify that the index jumps to the "Python" item.
+ //Verify that the index contains "Java" and "Javanese" items.
.andExpect(jsonPath("$._embedded.items",
- contains(ItemMatcher.matchItemWithTitleAndDateIssued(item3,
- "Python", "1990"),
- ItemMatcher.matchItemWithTitleAndDateIssued(item4,
- "Java", "1995-05-23")
+ contains(ItemMatcher.matchItemWithTitleAndDateIssued(item4,
+ "Java", "1995-05-23"),
+ ItemMatcher.matchItemWithTitleAndDateIssued(item3,
+ "Javanese", "1990")
)));
//** WHEN **
//An anonymous user browses the items in the Browse by Title endpoint
@@ -2031,7 +2026,7 @@ public void testBrowseByStartsWithAndPage() throws Exception {
.build();
Item item5 = ItemBuilder.createItem(context, col1)
- .withTitle("Python")
+ .withTitle("Java 2")
.withAuthor("Van Rossum, Guido")
.withIssueDate("1990")
.withSubject("Computing")
@@ -2056,9 +2051,10 @@ public void testBrowseByStartsWithAndPage() throws Exception {
// ---- BROWSES BY ITEM ----
//** WHEN **
- //An anonymous user browses the items in the Browse by date issued endpoint
- //with startsWith set to 199 and Page to 1
- getClient().perform(get("/api/discover/browses/dateissued/items?startsWith=199")
+ //An anonymous user browses the items in the Browse by title endpoint
+ //with startsWith set to Java and Page to 1
+ // DATASHARE - changed from dateissued to title browse (no dateissued browse in DataShare)
+ getClient().perform(get("/api/discover/browses/title/items?startsWith=Java")
.param("size", "1").param("page", "1"))
//** THEN **
@@ -2072,12 +2068,12 @@ public void testBrowseByStartsWithAndPage() throws Exception {
//We expect to jump to page 1 of the index
.andExpect(jsonPath("$.page.number", is(1)))
.andExpect(jsonPath("$.page.size", is(1)))
- .andExpect(jsonPath("$._links.self.href", containsString("startsWith=199")))
+ .andExpect(jsonPath("$._links.self.href", containsString("startsWith=Java")))
- //Verify that the index jumps to the "Java" item.
+ //Verify that the index jumps to the "Java 2" item.
.andExpect(jsonPath("$._embedded.items",
contains(
- ItemMatcher.matchItemWithTitleAndDateIssued(item3, "Java", "1995-05-23")
+ ItemMatcher.matchItemWithTitleAndDateIssued(item5, "Java 2", "1990")
)));
}
@@ -2308,7 +2304,8 @@ public void testBrowseByDateIssuedItemsFullProjectionTest() throws Exception {
context.restoreAuthSystemState();
- getClient().perform(get("/api/discover/browses/dateissued/items")
+ // DATASHARE - changed from dateissued to title browse (no dateissued browse in DataShare)
+ getClient().perform(get("/api/discover/browses/title/items")
.param("projection", "full"))
.andExpect(status().isOk())
@@ -2317,7 +2314,7 @@ public void testBrowseByDateIssuedItemsFullProjectionTest() throws Exception {
jsonPath("$._embedded.items[0]._embedded.owningCollection._embedded.adminGroup").doesNotExist());
String adminToken = getAuthToken(admin.getEmail(), password);
- getClient(adminToken).perform(get("/api/discover/browses/dateissued/items")
+ getClient(adminToken).perform(get("/api/discover/browses/title/items")
.param("projection", "full"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.items[0]._embedded.owningCollection._embedded.adminGroup",
diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java
index a9ab4f0b57a4..216e8d34b3c2 100644
--- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java
+++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java
@@ -98,18 +98,25 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest
/**
* This field has been created to easily modify the tests when updating the defaultConfiguration's sidebar facets
*/
+ // DATASHARE - added dateAccessioned and dateEmbargo facets
List> customSidebarFacets = List.of(
+ FacetEntryMatcher.matchFacet(false, "dateAccessioned", "date"),
+ FacetEntryMatcher.matchFacet(false, "dateEmbargo", "date")
);
/**
* This field has been created to easily modify the tests when updating the defaultConfiguration's search filters
*/
+ // DATASHARE - added dateAccessioned and dateEmbargo search filters
List> customSearchFilters = List.of(
+ SearchFilterMatcher.dateAccessionedFilter(),
+ SearchFilterMatcher.dateEmbargoFilter()
);
/**
* This field has been created to easily modify the tests when updating the defaultConfiguration's sort fields
*/
+ // DATASHARE - added dateEmbargo sort (dateAccessioned already in standard list)
List> customSortFields = List.of(
);
@@ -1241,8 +1248,7 @@ public void discoverSearchTest() throws Exception {
SearchFilterMatcher.isJournalOfPublicationRelation()
));
- List> allExpectedSortFields = new ArrayList<>(customSortFields);
- allExpectedSortFields.addAll(List.of(
+ List> allExpectedSortFields = new ArrayList<>(List.of(
SortOptionMatcher.sortOptionMatcher(
"score", DiscoverySortFieldConfiguration.SORT_ORDER.desc.name()),
SortOptionMatcher.sortOptionMatcher(
@@ -1250,7 +1256,10 @@ public void discoverSearchTest() throws Exception {
SortOptionMatcher.sortOptionMatcher(
"dc.date.issued", DiscoverySortFieldConfiguration.SORT_ORDER.desc.name()),
SortOptionMatcher.sortOptionMatcher(
- "dc.date.accessioned", DiscoverySortFieldConfiguration.SORT_ORDER.desc.name())
+ "dc.date.accessioned", DiscoverySortFieldConfiguration.SORT_ORDER.desc.name()),
+ // DATASHARE - added dateEmbargo sort
+ SortOptionMatcher.sortOptionMatcher(
+ "dc.date.embargo", DiscoverySortFieldConfiguration.SORT_ORDER.desc.name())
));
//When calling this root endpoint
@@ -1322,7 +1331,7 @@ public void discoverSearchByFieldNotConfiguredTest() throws Exception {
context.restoreAuthSystemState();
getClient().perform(get("/api/discover/search/objects")
- .param("sort", "dc.date.accessioned, ASC")
+ .param("sort", "dc.date.available, ASC")
.param("configuration", "workspace"))
.andExpect(status().isUnprocessableEntity());
}
@@ -4549,7 +4558,8 @@ public void discoverSearchObjectsWorkspaceConfigurationTest() throws Exception {
.andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(
FacetEntryMatcher.resourceTypeFacet(false),
FacetEntryMatcher.typeFacet(false),
- FacetEntryMatcher.dateIssuedFacet(false)
+ FacetEntryMatcher.matchFacet(false, "dateAccessioned", "date"),
+ FacetEntryMatcher.matchFacet(false, "dateEmbargo", "date")
)))
//There always needs to be a self link
.andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects")))
@@ -4595,7 +4605,8 @@ public void discoverSearchObjectsWorkspaceConfigurationTest() throws Exception {
.andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(
FacetEntryMatcher.resourceTypeFacet(false),
FacetEntryMatcher.typeFacet(false),
- FacetEntryMatcher.dateIssuedFacet(false)
+ FacetEntryMatcher.matchFacet(false, "dateAccessioned", "date"),
+ FacetEntryMatcher.matchFacet(false, "dateEmbargo", "date")
)))
//There always needs to be a self link
.andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects")))
@@ -4779,7 +4790,8 @@ public void discoverSearchObjectsWorkflowConfigurationTest() throws Exception {
.andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(
FacetEntryMatcher.resourceTypeFacet(false),
FacetEntryMatcher.typeFacet(false),
- FacetEntryMatcher.dateIssuedFacet(false),
+ FacetEntryMatcher.matchFacet(false, "dateAccessioned", "date"),
+ FacetEntryMatcher.matchFacet(false, "dateEmbargo", "date"),
FacetEntryMatcher.submitterFacet(false)
)))
//There always needs to be a self link
@@ -4829,7 +4841,8 @@ public void discoverSearchObjectsWorkflowConfigurationTest() throws Exception {
.andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(
FacetEntryMatcher.resourceTypeFacet(false),
FacetEntryMatcher.typeFacet(false),
- FacetEntryMatcher.dateIssuedFacet(false),
+ FacetEntryMatcher.matchFacet(false, "dateAccessioned", "date"),
+ FacetEntryMatcher.matchFacet(false, "dateEmbargo", "date"),
FacetEntryMatcher.submitterFacet(false)
)))
//There always needs to be a self link
@@ -4862,7 +4875,8 @@ public void discoverSearchObjectsWorkflowConfigurationTest() throws Exception {
.andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(
FacetEntryMatcher.resourceTypeFacet(false),
FacetEntryMatcher.typeFacet(false),
- FacetEntryMatcher.dateIssuedFacet(false),
+ FacetEntryMatcher.matchFacet(false, "dateAccessioned", "date"),
+ FacetEntryMatcher.matchFacet(false, "dateEmbargo", "date"),
FacetEntryMatcher.submitterFacet(false)
)))
//There always needs to be a self link
@@ -5076,7 +5090,8 @@ public void discoverSearchObjectsWorkflowAdminConfigurationTest() throws Excepti
.andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(
FacetEntryMatcher.resourceTypeFacet(false),
FacetEntryMatcher.typeFacet(false),
- FacetEntryMatcher.dateIssuedFacet(false),
+ FacetEntryMatcher.matchFacet(false, "dateAccessioned", "date"),
+ FacetEntryMatcher.matchFacet(false, "dateEmbargo", "date"),
FacetEntryMatcher.submitterFacet(false)
)))
//There always needs to be a self link
@@ -5824,7 +5839,8 @@ public void discoverSearchPoolTaskObjectsTest() throws Exception {
getClient(adminToken).perform(get("/api/discover/search/objects")
.param("configuration", "workflow")
- .param("sort", "dc.date.issued,DESC")
+ // DATASHARE - sortDateIssued commented in workflowConfiguration, use accessioned
+ .param("sort", "dc.date.accessioned,DESC")
.param("query", "Mathematical Theory"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.query", is("Mathematical Theory")))
@@ -5836,7 +5852,8 @@ public void discoverSearchPoolTaskObjectsTest() throws Exception {
getClient(adminToken).perform(get("/api/discover/search/objects")
.param("configuration", "workflow")
- .param("sort", "dc.date.issued,DESC")
+ // DATASHARE - sortDateIssued commented in workflowConfiguration, use accessioned
+ .param("sort", "dc.date.accessioned,DESC")
.param("query", "Metaphysics"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.query", is("Metaphysics")))
@@ -5900,7 +5917,8 @@ public void discoverSearchPoolTaskObjectsEmptyQueryTest() throws Exception {
getClient(adminToken).perform(get("/api/discover/search/objects")
.param("configuration", "workflow")
- .param("sort", "dc.date.issued,DESC")
+ // DATASHARE - sortDateIssued commented in workflowConfiguration, use accessioned
+ .param("sort", "dc.date.accessioned,DESC")
.param("query", ""))
.andExpect(status().isOk())
.andExpect(jsonPath("$.configuration", is("workflow")))
@@ -6850,12 +6868,13 @@ public void discoverSearchObjectsSupervisionConfigurationTest() throws Exception
.andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder(
FacetEntryMatcher.resourceTypeFacet(false),
FacetEntryMatcher.typeFacet(false),
- FacetEntryMatcher.dateIssuedFacet(false),
+ FacetEntryMatcher.matchFacet(false, "dateAccessioned", "date"),
+ FacetEntryMatcher.matchFacet(false, "dateEmbargo", "date"),
FacetEntryMatcher.submitterFacet(false),
FacetEntryMatcher.supervisedByFacet(false)
)))
//check supervisedBy Facet values
- .andExpect(jsonPath("$._embedded.facets[4]._embedded.values",
+ .andExpect(jsonPath("$._embedded.facets[5]._embedded.values",
contains(
entrySupervisedBy(groupA.getName(), groupA.getID().toString(), 6),
entrySupervisedBy(groupB.getName(), groupB.getID().toString(), 2)
diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryScopeBasedRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryScopeBasedRestControllerIT.java
index a3408a7736df..525ec947d92c 100644
--- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryScopeBasedRestControllerIT.java
+++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/DiscoveryScopeBasedRestControllerIT.java
@@ -504,6 +504,9 @@ public void ScopeBasedIndexingAndSearchTestParentCommunity2() throws Exception {
.andExpect(jsonPath("$._embedded.facets", containsInAnyOrder(
FacetEntryMatcher.authorFacet(false),
FacetEntryMatcher.subjectFacet(false),
+ // DATASHARE - added dateAccessioned and dateEmbargo facets
+ FacetEntryMatcher.matchFacet(false, "dateAccessioned", "date"),
+ FacetEntryMatcher.matchFacet(false, "dateEmbargo", "date"),
FacetEntryMatcher.dateIssuedFacet(false),
FacetEntryMatcher.hasContentInOriginalBundleFacet(false),
FacetEntryMatcher.entityTypeFacet(false)
@@ -616,6 +619,9 @@ public void ScopeBasedIndexingAndSearchTestSubcommunity22() throws Exception {
.andExpect(jsonPath("$._embedded.facets", containsInAnyOrder(
FacetEntryMatcher.authorFacet(false),
FacetEntryMatcher.subjectFacet(false),
+ // DATASHARE - added dateAccessioned and dateEmbargo facets
+ FacetEntryMatcher.matchFacet(false, "dateAccessioned", "date"),
+ FacetEntryMatcher.matchFacet(false, "dateEmbargo", "date"),
FacetEntryMatcher.dateIssuedFacet(false),
FacetEntryMatcher.hasContentInOriginalBundleFacet(false),
FacetEntryMatcher.entityTypeFacet(false)
@@ -666,6 +672,9 @@ public void ScopeBasedIndexingAndSearchTestCollection222() throws Exception {
.andExpect(jsonPath("$._embedded.facets", containsInAnyOrder(
FacetEntryMatcher.authorFacet(false),
FacetEntryMatcher.subjectFacet(false),
+ // DATASHARE - added dateAccessioned and dateEmbargo facets
+ FacetEntryMatcher.matchFacet(false, "dateAccessioned", "date"),
+ FacetEntryMatcher.matchFacet(false, "dateEmbargo", "date"),
FacetEntryMatcher.dateIssuedFacet(false),
FacetEntryMatcher.hasContentInOriginalBundleFacet(false),
FacetEntryMatcher.entityTypeFacet(false)
diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java
index 7a3bc738eb66..27f306a86863 100644
--- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java
+++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VocabularyRestRepositoryIT.java
@@ -157,6 +157,7 @@ public void findAllTest() throws Exception {
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.vocabularies", Matchers.containsInAnyOrder(
VocabularyMatcher.matchProperties("srsc", "srsc", false, true),
+ VocabularyMatcher.matchProperties("jacs", "jacs", false, true),
VocabularyMatcher.matchProperties("common_types", "common_types", true, false),
VocabularyMatcher.matchProperties("common_iso_languages", "common_iso_languages", true , false),
VocabularyMatcher.matchProperties("SolrAuthorAuthority", "SolrAuthorAuthority", false , false),
@@ -165,7 +166,7 @@ public void findAllTest() throws Exception {
)))
.andExpect(jsonPath("$._links.self.href",
Matchers.containsString("api/submission/vocabularies")))
- .andExpect(jsonPath("$.page.totalElements", is(6)));
+ .andExpect(jsonPath("$.page.totalElements", is(7)));
}
@Test
diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BitstreamMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BitstreamMatcher.java
index 36fc2f2aa131..7c3921c0bdd6 100644
--- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BitstreamMatcher.java
+++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BitstreamMatcher.java
@@ -100,6 +100,7 @@ private static Matcher super Object> matchFormat() {
*/
public static Matcher super Object> matchFullEmbeds() {
return matchEmbeds(
+ "accessStatus",
"bundle",
"format",
"thumbnail"
@@ -111,6 +112,7 @@ public static Matcher super Object> matchFullEmbeds() {
*/
public static Matcher super Object> matchLinks(UUID uuid) {
return HalMatcher.matchLinks(REST_SERVER_URL + "core/bitstreams/" + uuid,
+ "accessStatus",
"bundle",
"content",
"format",
diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BrowseIndexMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BrowseIndexMatcher.java
index 80f27b6bbbeb..44968373ac13 100644
--- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BrowseIndexMatcher.java
+++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BrowseIndexMatcher.java
@@ -32,12 +32,16 @@ private BrowseIndexMatcher() { }
public static Matcher super Object> subjectBrowseIndex(final String order) {
return allOf(
+ // DATASHARE - both upstream (dc.subject.*) and DataShare (dc.subject) sections exist in dspace.cfg;
+ // DSpace 8 uses first-wins for duplicate properties, so dc.subject.* is effective
hasJsonPath("$.metadata", contains("dc.subject.*")),
hasJsonPath("$.browseType", equalToIgnoringCase(BROWSE_TYPE_VALUE_LIST)),
hasJsonPath("$.type", equalToIgnoringCase("browse")),
hasJsonPath("$.dataType", equalToIgnoringCase("text")),
hasJsonPath("$.order", equalToIgnoringCase(order)),
- hasJsonPath("$.sortOptions[*].name", containsInAnyOrder("title", "dateissued", "dateaccessioned")),
+ // Sort options: first-wins gives upstream values (title, dateissued, dateaccessioned)
+ hasJsonPath("$.sortOptions[*].name",
+ containsInAnyOrder("title", "dateissued", "dateaccessioned")),
hasJsonPath("$._links.self.href", is(REST_SERVER_URL + "discover/browses/subject")),
hasJsonPath("$._links.entries.href", is(REST_SERVER_URL + "discover/browses/subject/entries")),
hasJsonPath("$._links.items.href", is(REST_SERVER_URL + "discover/browses/subject/items"))
@@ -51,7 +55,9 @@ public static Matcher super Object> titleBrowseIndex(final String order) {
hasJsonPath("$.type", equalToIgnoringCase("browse")),
hasJsonPath("$.dataType", equalToIgnoringCase("title")),
hasJsonPath("$.order", equalToIgnoringCase(order)),
- hasJsonPath("$.sortOptions[*].name", containsInAnyOrder("title", "dateissued", "dateaccessioned")),
+ // Sort options: first-wins gives upstream values (title, dateissued, dateaccessioned)
+ hasJsonPath("$.sortOptions[*].name",
+ containsInAnyOrder("title", "dateissued", "dateaccessioned")),
hasJsonPath("$._links.self.href", is(REST_SERVER_URL + "discover/browses/title")),
hasJsonPath("$._links.items.href", is(REST_SERVER_URL + "discover/browses/title/items"))
);
@@ -64,7 +70,9 @@ public static Matcher super Object> contributorBrowseIndex(final String order)
hasJsonPath("$.type", equalToIgnoringCase("browse")),
hasJsonPath("$.dataType", equalToIgnoringCase("text")),
hasJsonPath("$.order", equalToIgnoringCase(order)),
- hasJsonPath("$.sortOptions[*].name", containsInAnyOrder("title", "dateissued", "dateaccessioned")),
+ // Sort options: first-wins gives upstream values (title, dateissued, dateaccessioned)
+ hasJsonPath("$.sortOptions[*].name",
+ containsInAnyOrder("title", "dateissued", "dateaccessioned")),
hasJsonPath("$._links.self.href", is(REST_SERVER_URL + "discover/browses/author")),
hasJsonPath("$._links.entries.href", is(REST_SERVER_URL + "discover/browses/author/entries")),
hasJsonPath("$._links.items.href", is(REST_SERVER_URL + "discover/browses/author/items"))
@@ -78,7 +86,9 @@ public static Matcher super Object> dateIssuedBrowseIndex(final String order)
hasJsonPath("$.type", equalToIgnoringCase("browse")),
hasJsonPath("$.dataType", equalToIgnoringCase("date")),
hasJsonPath("$.order", equalToIgnoringCase(order)),
- hasJsonPath("$.sortOptions[*].name", containsInAnyOrder("title", "dateissued", "dateaccessioned")),
+ // Sort options: first-wins gives upstream values (title, dateissued, dateaccessioned)
+ hasJsonPath("$.sortOptions[*].name",
+ containsInAnyOrder("title", "dateissued", "dateaccessioned")),
hasJsonPath("$._links.self.href", is(REST_SERVER_URL + "discover/browses/dateissued")),
hasJsonPath("$._links.items.href", is(REST_SERVER_URL + "discover/browses/dateissued/items"))
);
@@ -101,4 +111,39 @@ public static Matcher super Object> hierarchicalBrowseIndex(final String vocab
is(REST_SERVER_URL + String.format("discover/browses/%s", vocabulary)))
);
}
+
+ // DATASHARE - start
+ public static Matcher super Object> subjectClassificationBrowseIndex(final String order) {
+ return allOf(
+ hasJsonPath("$.metadata", contains("dc.subject.classification")),
+ hasJsonPath("$.browseType", equalToIgnoringCase(BROWSE_TYPE_VALUE_LIST)),
+ hasJsonPath("$.type", equalToIgnoringCase("browse")),
+ hasJsonPath("$.dataType", equalToIgnoringCase("text")),
+ hasJsonPath("$.order", equalToIgnoringCase(order)),
+ // Sort options: first-wins gives upstream values (title, dateissued, dateaccessioned)
+ hasJsonPath("$.sortOptions[*].name",
+ containsInAnyOrder("title", "dateissued", "dateaccessioned")),
+ hasJsonPath("$._links.self.href",
+ is(REST_SERVER_URL + "discover/browses/subject_classification")),
+ hasJsonPath("$._links.entries.href",
+ is(REST_SERVER_URL + "discover/browses/subject_classification/entries")),
+ hasJsonPath("$._links.items.href", is(REST_SERVER_URL + "discover/browses/subject_classification/items"))
+ );
+ }
+
+ public static Matcher super Object> dateAccessionedBrowseIndex(final String order) {
+ return allOf(
+ hasJsonPath("$.metadata", contains("dc.date.accessioned")),
+ hasJsonPath("$.browseType", equalToIgnoringCase(BROWSE_TYPE_FLAT)),
+ hasJsonPath("$.type", equalToIgnoringCase("browse")),
+ hasJsonPath("$.dataType", equalToIgnoringCase("date")),
+ hasJsonPath("$.order", equalToIgnoringCase(order)),
+ // Sort options: first-wins gives upstream values (title, dateissued, dateaccessioned)
+ hasJsonPath("$.sortOptions[*].name",
+ containsInAnyOrder("title", "dateissued", "dateaccessioned")),
+ hasJsonPath("$._links.self.href", is(REST_SERVER_URL + "discover/browses/dateaccessioned")),
+ hasJsonPath("$._links.items.href", is(REST_SERVER_URL + "discover/browses/dateaccessioned/items"))
+ );
+ }
+ // DATASHARE - end
}
diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SearchFilterMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SearchFilterMatcher.java
index 8238d378df4a..03d56c640e34 100644
--- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SearchFilterMatcher.java
+++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SearchFilterMatcher.java
@@ -156,4 +156,26 @@ public static Matcher super Object> isJournalOfPublicationRelation() {
checkOperators()
);
}
+
+ // DATASHARE - start
+ public static Matcher super Object> dateAccessionedFilter() {
+ return allOf(
+ hasJsonPath("$.filter", is("dateAccessioned")),
+ hasJsonPath("$.hasFacets", is(true)),
+ hasJsonPath("$.type", is("date")),
+ hasJsonPath("$.openByDefault", is(false)),
+ checkOperators()
+ );
+ }
+
+ public static Matcher super Object> dateEmbargoFilter() {
+ return allOf(
+ hasJsonPath("$.filter", is("dateEmbargo")),
+ hasJsonPath("$.hasFacets", is(true)),
+ hasJsonPath("$.type", is("date")),
+ hasJsonPath("$.openByDefault", is(false)),
+ checkOperators()
+ );
+ }
+ // DATASHARE - end
}
diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/submit/step/datashare/DatashareSpatialAndTemporalStepTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/submit/step/datashare/DatashareSpatialAndTemporalStepTest.java
new file mode 100644
index 000000000000..16a3898b2b54
--- /dev/null
+++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/submit/step/datashare/DatashareSpatialAndTemporalStepTest.java
@@ -0,0 +1,102 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.rest.submit.step.datashare;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for the encode/decode time period logic in
+ * {@link DatashareSpatialAndTemporalStep}.
+ */
+public class DatashareSpatialAndTemporalStepTest {
+
+ // ---- encodeTimePeriod tests ----
+
+ @Test
+ public void testEncodeFullDates() {
+ String result = DatashareSpatialAndTemporalStep.encodeTimePeriod("2019-02-21", "2020-03-18");
+ assertEquals("start=2019-02-21; end=2020-03-18; scheme=W3C-DTF", result);
+ }
+
+ @Test
+ public void testEncodeYearOnly() {
+ String result = DatashareSpatialAndTemporalStep.encodeTimePeriod("2021", "2025");
+ assertEquals("start=2021; end=2025; scheme=W3C-DTF", result);
+ }
+
+ @Test
+ public void testEncodeYearMonth() {
+ String result = DatashareSpatialAndTemporalStep.encodeTimePeriod("2021-03", "2022-11");
+ assertEquals("start=2021-03; end=2022-11; scheme=W3C-DTF", result);
+ }
+
+ @Test
+ public void testEncodeNullStart() {
+ String result = DatashareSpatialAndTemporalStep.encodeTimePeriod(null, "2025");
+ assertEquals("start=; end=2025; scheme=W3C-DTF", result);
+ }
+
+ @Test
+ public void testEncodeNullEnd() {
+ String result = DatashareSpatialAndTemporalStep.encodeTimePeriod("2021", null);
+ assertEquals("start=2021; end=; scheme=W3C-DTF", result);
+ }
+
+ // ---- decodeTimePeriod tests ----
+
+ @Test
+ public void testDecodeFullDates() {
+ String[] result = DatashareSpatialAndTemporalStep.decodeTimePeriod(
+ "start=2019-02-21; end=2020-03-18; scheme=W3C-DTF");
+ assertArrayEquals(new String[]{"2019-02-21", "2020-03-18"}, result);
+ }
+
+ @Test
+ public void testDecodeYearOnly() {
+ String[] result = DatashareSpatialAndTemporalStep.decodeTimePeriod(
+ "start=2021; end=2025; scheme=W3C-DTF");
+ assertArrayEquals(new String[]{"2021", "2025"}, result);
+ }
+
+ @Test
+ public void testDecodeYearMonth() {
+ String[] result = DatashareSpatialAndTemporalStep.decodeTimePeriod(
+ "start=2021-03; end=2022-11; scheme=W3C-DTF");
+ assertArrayEquals(new String[]{"2021-03", "2022-11"}, result);
+ }
+
+ @Test
+ public void testDecodeNull() {
+ String[] result = DatashareSpatialAndTemporalStep.decodeTimePeriod(null);
+ assertNull(result);
+ }
+
+ @Test
+ public void testDecodeEmpty() {
+ String[] result = DatashareSpatialAndTemporalStep.decodeTimePeriod("");
+ assertNull(result);
+ }
+
+ @Test
+ public void testDecodeEmptyDates() {
+ String[] result = DatashareSpatialAndTemporalStep.decodeTimePeriod(
+ "start=; end=; scheme=W3C-DTF");
+ assertArrayEquals(new String[]{"", ""}, result);
+ }
+
+ @Test
+ public void testRoundTrip() {
+ String encoded = DatashareSpatialAndTemporalStep.encodeTimePeriod("2021-03-15", "2023-12-31");
+ String[] decoded = DatashareSpatialAndTemporalStep.decodeTimePeriod(encoded);
+ assertArrayEquals(new String[]{"2021-03-15", "2023-12-31"}, decoded);
+ }
+}
diff --git a/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java b/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java
index 8c0744a09cce..2ba8af4623be 100644
--- a/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java
+++ b/dspace-server-webapp/src/test/java/org/dspace/curate/CurationScriptIT.java
@@ -667,7 +667,7 @@ public void testURLRedirectCurateTest() throws Exception {
// MetadataValueLinkChecker uri field with regular link
.withMetadata("dc", "description", null, "https://google.com")
// MetadataValueLinkChecker uri field with redirect link
- .withMetadata("dc", "description", "uri", "https://demo7.dspace.org/handle/123456789/1")
+ .withMetadata("dc", "description", "uri", "http://google.com")
// MetadataValueLinkChecker uri field with non resolving link
.withMetadata("dc", "description", "uri", "https://www.atmire.com/broken-link")
.withSubject("ExtraEntry")
@@ -690,8 +690,8 @@ public void testURLRedirectCurateTest() throws Exception {
// field that should be ignored
assertFalse(checkIfInfoTextLoggedByHandler(handler, "demo.dspace.org/home"));
- // redirect links in field that should not be ignored (https) => expect OK
- assertTrue(checkIfInfoTextLoggedByHandler(handler, "https://demo7.dspace.org/handle/123456789/1 = 200 - OK"));
+ // redirect links in field that should not be ignored => expect OK (even though curl responds with 301)
+ assertTrue(checkIfInfoTextLoggedByHandler(handler, "http://google.com = 200 - OK"));
// regular link in field that should not be ignored (http) => expect OK
assertTrue(checkIfInfoTextLoggedByHandler(handler, "https://google.com = 200 - OK"));
// nonexistent link in field that should not be ignored => expect 404
diff --git a/dspace/config/crosswalks/DIM2DataCite.xsl b/dspace/config/crosswalks/DIM2DataCite.xsl
index 7ad39273ea5b..c1a6ea497793 100644
--- a/dspace/config/crosswalks/DIM2DataCite.xsl
+++ b/dspace/config/crosswalks/DIM2DataCite.xsl
@@ -258,7 +258,7 @@
-->
-
+
@@ -327,7 +327,6 @@
-
+
@@ -625,7 +624,8 @@
@@ -636,6 +636,15 @@
+
+
+ https://creativecommons.org/licenses/by/4.0/
+ CC-BY-4.0
+ SPDX
+ https://spdx.org/licenses/
+
+
+
diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml
index e9aed4592022..6f3576c0961c 100644
--- a/dspace/config/spring/api/discovery.xml
+++ b/dspace/config/spring/api/discovery.xml
@@ -237,12 +237,11 @@
+
-
-
diff --git a/dspace/config/submission-forms.xml b/dspace/config/submission-forms.xml
index dec1b7432b0b..897f3a6b2322 100644
--- a/dspace/config/submission-forms.xml
+++ b/dspace/config/submission-forms.xml
@@ -155,9 +155,9 @@
Funder *****-->
- ds
- funder
- dropdown-value
+ dc
+ contributor
+ other
true
dropdown
@@ -421,9 +421,9 @@