Merge remote-tracking branch 'upstream/main' into issue-1712_w2p-97080_facet-search-all-words-main

Merge remote-tracking branch 'upstream/main' into
issue-1712_w2p-97080_facet-search-all-words-main
This commit is contained in:
Nona Luypaert
2023-01-13 12:10:26 +01:00
14 changed files with 915 additions and 255 deletions

View File

@@ -0,0 +1,38 @@
/**
* 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.importer.external.datacite;
import java.util.Map;
import javax.annotation.Resource;
import org.dspace.importer.external.metadatamapping.AbstractMetadataFieldMapping;
/**
* An implementation of {@link AbstractMetadataFieldMapping}
* Responsible for defining the mapping of the datacite metadatum fields on the DSpace metadatum fields
*
* @author Pasquale Cavallo (pasquale.cavallo at 4science dot it)
* @author Florian Gantner (florian.gantner@uni-bamberg.de)
*/
public class DataCiteFieldMapping extends AbstractMetadataFieldMapping {
/**
* Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it
* only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over
* what metadatafield is generated.
*
* @param metadataFieldMap The map containing the link between retrieve metadata and metadata that will be set to
* the item.
*/
@Override
@Resource(name = "dataciteMetadataFieldMap")
public void setMetadataFieldMap(Map metadataFieldMap) {
super.setMetadataFieldMap(metadataFieldMap);
}
}

View File

@@ -0,0 +1,168 @@
/**
* 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.importer.external.datacite;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.el.MethodNotFoundException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.content.Item;
import org.dspace.importer.external.datamodel.ImportRecord;
import org.dspace.importer.external.datamodel.Query;
import org.dspace.importer.external.exception.MetadataSourceException;
import org.dspace.importer.external.liveimportclient.service.LiveImportClient;
import org.dspace.importer.external.service.AbstractImportMetadataSourceService;
import org.dspace.importer.external.service.DoiCheck;
import org.dspace.importer.external.service.components.QuerySource;
import org.dspace.services.ConfigurationService;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Implements a data source for querying Datacite
* Mainly copied from CrossRefImportMetadataSourceServiceImpl.
*
* optional Affiliation informations are not part of the API request.
* https://support.datacite.org/docs/can-i-see-more-detailed-affiliation-information-in-the-rest-api
*
* @author Pasquale Cavallo (pasquale.cavallo at 4science dot it)
* @author Florian Gantner (florian.gantner@uni-bamberg.de)
*
*/
public class DataCiteImportMetadataSourceServiceImpl
extends AbstractImportMetadataSourceService<String> implements QuerySource {
private final static Logger log = LogManager.getLogger();
@Autowired
private LiveImportClient liveImportClient;
@Autowired
private ConfigurationService configurationService;
@Override
public String getImportSource() {
return "datacite";
}
@Override
public void init() throws Exception {
}
@Override
public ImportRecord getRecord(String recordId) throws MetadataSourceException {
Collection<ImportRecord> records = getRecords(recordId, 0, 1);
if (records.size() == 0) {
return null;
}
return records.stream().findFirst().get();
}
@Override
public int getRecordsCount(String query) throws MetadataSourceException {
Collection<ImportRecord> records = getRecords(query, 0, -1);
return records == null ? 0 : records.size();
}
@Override
public int getRecordsCount(Query query) throws MetadataSourceException {
String id = getID(query.toString());
return getRecordsCount(StringUtils.isBlank(id) ? query.toString() : id);
}
@Override
public Collection<ImportRecord> getRecords(String query, int start, int count) throws MetadataSourceException {
List<ImportRecord> records = new ArrayList<>();
String id = getID(query);
Map<String, Map<String, String>> params = new HashMap<>();
Map<String, String> uriParameters = new HashMap<>();
params.put("uriParameters", uriParameters);
if (StringUtils.isBlank(id)) {
id = query;
}
uriParameters.put("query", id);
int timeoutMs = configurationService.getIntProperty("datacite.timeout", 180000);
String url = configurationService.getProperty("datacite.url", "https://api.datacite.org/dois/");
String responseString = liveImportClient.executeHttpGetRequest(timeoutMs, url, params);
JsonNode jsonNode = convertStringJsonToJsonNode(responseString);
if (jsonNode == null) {
log.warn("DataCite returned invalid JSON");
return records;
}
JsonNode dataNode = jsonNode.at("/data");
if (dataNode.isArray()) {
Iterator<JsonNode> iterator = dataNode.iterator();
while (iterator.hasNext()) {
JsonNode singleDoiNode = iterator.next();
String json = singleDoiNode.at("/attributes").toString();
records.add(transformSourceRecords(json));
}
} else {
String json = dataNode.at("/attributes").toString();
records.add(transformSourceRecords(json));
}
return records;
}
private JsonNode convertStringJsonToJsonNode(String json) {
try {
return new ObjectMapper().readTree(json);
} catch (JsonProcessingException e) {
log.error("Unable to process json response.", e);
}
return null;
}
@Override
public Collection<ImportRecord> getRecords(Query query) throws MetadataSourceException {
String id = getID(query.toString());
return getRecords(StringUtils.isBlank(id) ? query.toString() : id, 0, -1);
}
@Override
public ImportRecord getRecord(Query query) throws MetadataSourceException {
String id = getID(query.toString());
return getRecord(StringUtils.isBlank(id) ? query.toString() : id);
}
@Override
public Collection<ImportRecord> findMatchingRecords(Query query) throws MetadataSourceException {
String id = getID(query.toString());
return getRecords(StringUtils.isBlank(id) ? query.toString() : id, 0, -1);
}
@Override
public Collection<ImportRecord> findMatchingRecords(Item item) throws MetadataSourceException {
throw new MethodNotFoundException("This method is not implemented for DataCite");
}
public String getID(String query) {
if (DoiCheck.isDoi(query)) {
return query;
}
// Workaround for encoded slashes.
if (query.contains("%252F")) {
query = query.replace("%252F", "/");
}
if (DoiCheck.isDoi(query)) {
return query;
}
return StringUtils.EMPTY;
}
}

View File

@@ -26,6 +26,7 @@ import java.util.regex.Pattern;
import javax.sql.DataSource;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.core.Context;
@@ -37,6 +38,7 @@ import org.dspace.workflow.factory.WorkflowServiceFactory;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.FlywayException;
import org.flywaydb.core.api.MigrationInfo;
import org.flywaydb.core.api.MigrationVersion;
import org.flywaydb.core.api.callback.Callback;
import org.flywaydb.core.api.configuration.FluentConfiguration;
import org.flywaydb.core.internal.info.MigrationInfoDumper;
@@ -93,7 +95,7 @@ public class DatabaseUtils {
// Usage checks
if (argv.length < 1) {
System.out.println("\nDatabase action argument is missing.");
System.out.println("Valid actions: 'test', 'info', 'migrate', 'repair', 'validate', " +
System.out.println("Valid actions: 'test', 'info', 'migrate', 'repair', 'skip', 'validate', " +
"'update-sequences' or 'clean'");
System.out.println("\nOr, type 'database help' for more information.\n");
System.exit(1);
@@ -111,280 +113,339 @@ public class DatabaseUtils {
// *before* any other Flyway commands can be run. This is a safety check.
FlywayUpgradeUtils.upgradeFlywayTable(flyway, dataSource.getConnection());
// "test" = Test Database Connection
if (argv[0].equalsIgnoreCase("test")) {
// Try to connect to the database
System.out.println("\nAttempting to connect to database");
try (Connection connection = dataSource.getConnection()) {
System.out.println("Connected successfully!");
// Determine action param passed to "./dspace database"
switch (argv[0].toLowerCase(Locale.ENGLISH)) {
// "test" = Test Database Connection
case "test":
// Try to connect to the database
System.out.println("\nAttempting to connect to database");
try (Connection connection = dataSource.getConnection()) {
System.out.println("Connected successfully!");
// Print basic database connection information
printDBInfo(connection);
// Print basic database connection information
printDBInfo(connection);
// Print any database warnings/errors found (if any)
boolean issueFound = printDBIssues(connection);
// Print any database warnings/errors found (if any)
boolean issueFound = printDBIssues(connection);
// If issues found, exit with an error status (even if connection succeeded).
if (issueFound) {
System.exit(1);
} else {
System.exit(0);
}
} catch (SQLException sqle) {
System.err.println("\nError running 'test': ");
System.err.println(" - " + sqle);
System.err.println("\nPlease see the DSpace documentation for assistance.\n");
sqle.printStackTrace(System.err);
System.exit(1);
}
} else if (argv[0].equalsIgnoreCase("info") || argv[0].equalsIgnoreCase("status")) {
try (Connection connection = dataSource.getConnection()) {
// Print basic Database info
printDBInfo(connection);
// Get info table from Flyway
System.out.println("\n" + MigrationInfoDumper.dumpToAsciiTable(flyway.info().all()));
// If Flyway is NOT yet initialized, also print the determined version information
// NOTE: search is case sensitive, as flyway table name is ALWAYS lowercase,
// See: http://flywaydb.org/documentation/faq.html#case-sensitive
if (!tableExists(connection, flyway.getConfiguration().getTable(), true)) {
System.out
.println("\nNOTE: This database is NOT yet initialized for auto-migrations (via Flyway).");
// Determine which version of DSpace this looks like
String dbVersion = determineDBVersion(connection);
if (dbVersion != null) {
System.out
.println("\nYour database looks to be compatible with DSpace version " + dbVersion);
System.out.println(
"All upgrades *after* version " + dbVersion + " will be run during the next migration" +
".");
System.out.println("\nIf you'd like to upgrade now, simply run 'dspace database migrate'.");
}
}
// Print any database warnings/errors found (if any)
boolean issueFound = printDBIssues(connection);
// If issues found, exit with an error status
if (issueFound) {
System.exit(1);
} else {
System.exit(0);
}
} catch (SQLException e) {
System.err.println("Info exception:");
e.printStackTrace(System.err);
System.exit(1);
}
} else if (argv[0].equalsIgnoreCase("migrate")) {
try (Connection connection = dataSource.getConnection()) {
System.out.println("\nDatabase URL: " + connection.getMetaData().getURL());
// "migrate" allows for an OPTIONAL second argument (only one may be specified):
// - "ignored" = Also run any previously "ignored" migrations during the migration
// - "force" = Even if no pending migrations exist, still run a migration to trigger callbacks.
// - [version] = ONLY run migrations up to a specific DSpace version (ONLY FOR TESTING)
if (argv.length == 2) {
if (argv[1].equalsIgnoreCase("ignored")) {
System.out.println(
"Migrating database to latest version AND running previously \"Ignored\" " +
"migrations... (Check logs for details)");
// Update the database to latest version, but set "outOfOrder=true"
// This will ensure any old migrations in the "ignored" state are now run
updateDatabase(dataSource, connection, null, true);
} else if (argv[1].equalsIgnoreCase("force")) {
updateDatabase(dataSource, connection, null, false, true);
// If issues found, exit with an error status (even if connection succeeded).
if (issueFound) {
System.exit(1);
} else {
// Otherwise, we assume "argv[1]" is a valid migration version number
// This is only for testing! Never specify for Production!
System.exit(0);
}
} catch (SQLException sqle) {
System.err.println("\nError running 'test': ");
System.err.println(" - " + sqle);
System.err.println("\nPlease see the DSpace documentation for assistance.\n");
sqle.printStackTrace(System.err);
System.exit(1);
}
break;
// "info" and "status" are identical and provide database info
case "info":
case "status":
try (Connection connection = dataSource.getConnection()) {
// Print basic Database info
printDBInfo(connection);
// Get info table from Flyway
System.out.println("\n" + MigrationInfoDumper.dumpToAsciiTable(flyway.info().all()));
// If Flyway is NOT yet initialized, also print the determined version information
// NOTE: search is case sensitive, as flyway table name is ALWAYS lowercase,
// See: http://flywaydb.org/documentation/faq.html#case-sensitive
if (!tableExists(connection, flyway.getConfiguration().getTable(), true)) {
System.out
.println("\nNOTE: This database is NOT yet initialized for auto-migrations " +
"(via Flyway).");
// Determine which version of DSpace this looks like
String dbVersion = determineDBVersion(connection);
if (dbVersion != null) {
System.out
.println("\nYour database looks to be compatible with DSpace version " + dbVersion);
System.out.println(
"All upgrades *after* version " + dbVersion + " will be run during the next " +
"migration.");
System.out.println("\nIf you'd like to upgrade now, simply run 'dspace database " +
"migrate'.");
}
}
// Print any database warnings/errors found (if any)
boolean issueFound = printDBIssues(connection);
// If issues found, exit with an error status
if (issueFound) {
System.exit(1);
} else {
System.exit(0);
}
} catch (SQLException e) {
System.err.println("Info exception:");
e.printStackTrace(System.err);
System.exit(1);
}
break;
// "migrate" = Run all pending database migrations
case "migrate":
try (Connection connection = dataSource.getConnection()) {
System.out.println("\nDatabase URL: " + connection.getMetaData().getURL());
// "migrate" allows for an OPTIONAL second argument (only one may be specified):
// - "ignored" = Also run any previously "ignored" migrations during the migration
// - "force" = Even if no pending migrations exist, still run migrate to trigger callbacks.
// - [version] = ONLY run migrations up to a specific DSpace version (ONLY FOR TESTING)
if (argv.length == 2) {
if (argv[1].equalsIgnoreCase("ignored")) {
System.out.println(
"Migrating database to latest version AND running previously \"Ignored\" " +
"migrations... (Check logs for details)");
// Update the database to latest version, but set "outOfOrder=true"
// This will ensure any old migrations in the "ignored" state are now run
updateDatabase(dataSource, connection, null, true);
} else if (argv[1].equalsIgnoreCase("force")) {
updateDatabase(dataSource, connection, null, false, true);
} else {
// Otherwise, we assume "argv[1]" is a valid migration version number
// This is only for testing! Never specify for Production!
String migrationVersion = argv[1];
BufferedReader input = new BufferedReader(
new InputStreamReader(System.in, StandardCharsets.UTF_8));
System.out.println(
"You've specified to migrate your database ONLY to version " + migrationVersion +
" ...");
System.out.println(
"\nWARNING: In this mode, we DISABLE all callbacks, which means that you will " +
"need to manually update registries and manually run a reindex. This is " +
"because you are attempting to use an OLD version (" + migrationVersion + ") " +
"Database with a newer DSpace API. NEVER do this in a PRODUCTION scenario. " +
"The resulting database is only useful for migration testing.\n");
System.out.print(
"Are you SURE you only want to migrate your database to version " +
migrationVersion + "? [y/n]: ");
String choiceString = input.readLine();
input.close();
if (choiceString.equalsIgnoreCase("y")) {
System.out.println(
"Migrating database ONLY to version " + migrationVersion + " ... " +
"(Check logs for details)");
// Update the database, to the version specified.
updateDatabase(dataSource, connection, migrationVersion, false);
} else {
System.out.println("No action performed.");
}
}
} else {
System.out.println("Migrating database to latest version... " +
"(Check dspace logs for details)");
updateDatabase(dataSource, connection);
}
System.out.println("Done.");
System.exit(0);
} catch (SQLException e) {
System.err.println("Migration exception:");
e.printStackTrace(System.err);
System.exit(1);
}
break;
// "repair" = Run Flyway repair script
case "repair":
try (Connection connection = dataSource.getConnection();) {
System.out.println("\nDatabase URL: " + connection.getMetaData().getURL());
System.out.println(
"Attempting to repair any previously failed migrations (or mismatched checksums) via " +
"FlywayDB... (Check dspace logs for details)");
flyway.repair();
System.out.println("Done.");
System.exit(0);
} catch (SQLException | FlywayException e) {
System.err.println("Repair exception:");
e.printStackTrace(System.err);
System.exit(1);
}
break;
// "skip" = Skip a specific Flyway migration (by telling Flyway it succeeded)
case "skip":
try {
// "skip" requires a migration version to skip. Only that exact version will be skipped.
if (argv.length == 2) {
String migrationVersion = argv[1];
BufferedReader input = new BufferedReader(
new InputStreamReader(System.in, StandardCharsets.UTF_8));
new InputStreamReader(System.in, StandardCharsets.UTF_8));
System.out.println(
"You've specified to migrate your database ONLY to version " + migrationVersion + " " +
"You've specified to SKIP the migration with version='" + migrationVersion + "' " +
"...");
System.out.println(
"\nWARNING: In this mode, we DISABLE all callbacks, which means that you will need " +
"to manually update registries and manually run a reindex. This is because you " +
"are attempting to use an OLD version (" + migrationVersion + ") Database with " +
"a newer DSpace API. NEVER do this in a PRODUCTION scenario. The resulting " +
"database is only useful for migration testing.\n");
System.out.print(
"Are you SURE you only want to migrate your database to version " + migrationVersion
+ "? [y/n]: ");
"\nWARNING: You should only skip migrations which are no longer required or have " +
"become obsolete. Skipping a REQUIRED migration may result in DSpace failing " +
"to startup or function properly. Are you sure you want to SKIP the " +
"migration with version '" + migrationVersion + "'? [y/n]: ");
String choiceString = input.readLine();
input.close();
if (choiceString.equalsIgnoreCase("y")) {
System.out.println(
"Migrating database ONLY to version " + migrationVersion + " ... (Check logs for " +
"details)");
// Update the database, to the version specified.
updateDatabase(dataSource, connection, migrationVersion, false);
} else {
System.out.println("No action performed.");
"Attempting to skip migration with version " + migrationVersion + " " +
"... (Check logs for details)");
skipMigration(dataSource, migrationVersion);
}
} else {
System.out.println("The 'skip' command REQUIRES a version to be specified. " +
"Only that single migration will be skipped. For the list " +
"of migration versions use the 'info' command.");
}
} catch (IOException e) {
System.err.println("Exception when attempting to skip migration:");
e.printStackTrace(System.err);
System.exit(1);
}
break;
// "validate" = Run Flyway validation to check for database errors/issues
case "validate":
try (Connection connection = dataSource.getConnection();) {
System.out.println("\nDatabase URL: " + connection.getMetaData().getURL());
System.out
.println("Attempting to validate database status (and migration checksums) via " +
"FlywayDB...");
flyway.validate();
System.out.println("No errors thrown. Validation succeeded. (Check dspace logs for more " +
"details)");
System.exit(0);
} catch (SQLException | FlywayException e) {
System.err.println("Validation exception:");
e.printStackTrace(System.err);
System.exit(1);
}
break;
// "clean" = Run Flyway clean script
case "clean":
// If clean is disabled, return immediately
if (flyway.getConfiguration().isCleanDisabled()) {
System.out.println(
"\nWARNING: 'clean' command is currently disabled, as it is dangerous to run in " +
"Production scenarios!");
System.out.println(
"\nIn order to run a 'clean' you first must enable it in your DSpace config by " +
"specifying 'db.cleanDisabled=false'.\n");
System.exit(1);
}
try (Connection connection = dataSource.getConnection()) {
String dbType = getDbType(connection);
// Not all Postgres user accounts will be able to run a 'clean',
// as only 'superuser' accounts can remove the 'pgcrypto' extension.
if (dbType.equals(DBMS_POSTGRES)) {
// Check if database user has permissions suitable to run a clean
if (!PostgresUtils.checkCleanPermissions(connection)) {
String username = connection.getMetaData().getUserName();
// Exit immediately, providing a descriptive error message
System.out.println(
"\nERROR: The database user '" + username + "' does not have sufficient " +
"privileges to run a 'database clean' (via Flyway).");
System.out.println(
"\nIn order to run a 'clean', the database user MUST have 'superuser' privileges");
System.out.println(
"OR the '" + PostgresUtils.PGCRYPTO + "' extension must be installed in a " +
"separate schema (see documentation).");
System.out.println(
"\nOptionally, you could also manually remove the '" + PostgresUtils.PGCRYPTO +
"' extension first (DROP EXTENSION " + PostgresUtils.PGCRYPTO +
" CASCADE;), then rerun the 'clean'");
System.exit(1);
}
}
} else {
System.out.println("Migrating database to latest version... (Check dspace logs for details)");
updateDatabase(dataSource, connection);
}
System.out.println("Done.");
System.exit(0);
} catch (SQLException e) {
System.err.println("Migration exception:");
e.printStackTrace(System.err);
System.exit(1);
}
} else if (argv[0].equalsIgnoreCase("repair")) {
// "repair" = Run Flyway repair script
try (Connection connection = dataSource.getConnection();) {
System.out.println("\nDatabase URL: " + connection.getMetaData().getURL());
System.out.println(
"Attempting to repair any previously failed migrations (or mismatched checksums) via " +
"FlywayDB... (Check dspace logs for details)");
flyway.repair();
System.out.println("Done.");
System.exit(0);
} catch (SQLException | FlywayException e) {
System.err.println("Repair exception:");
e.printStackTrace(System.err);
System.exit(1);
}
} else if (argv[0].equalsIgnoreCase("validate")) {
// "validate" = Run Flyway validation to check for database errors/issues
BufferedReader input = new BufferedReader(new InputStreamReader(System.in,
StandardCharsets.UTF_8));
try (Connection connection = dataSource.getConnection();) {
System.out.println("\nDatabase URL: " + connection.getMetaData().getURL());
System.out
.println("Attempting to validate database status (and migration checksums) via FlywayDB...");
flyway.validate();
System.out.println("No errors thrown. Validation succeeded. (Check dspace logs for more details)");
System.exit(0);
} catch (SQLException | FlywayException e) {
System.err.println("Validation exception:");
e.printStackTrace(System.err);
System.exit(1);
}
} else if (argv[0].equalsIgnoreCase("clean")) {
// "clean" = Run Flyway clean script
// If clean is disabled, return immediately
if (flyway.getConfiguration().isCleanDisabled()) {
System.out.println(
"\nWARNING: 'clean' command is currently disabled, as it is dangerous to run in Production " +
"scenarios!");
System.out.println(
"\nIn order to run a 'clean' you first must enable it in your DSpace config by specifying 'db" +
".cleanDisabled=false'.\n");
System.exit(1);
}
try (Connection connection = dataSource.getConnection()) {
String dbType = getDbType(connection);
// Not all Postgres user accounts will be able to run a 'clean',
// as only 'superuser' accounts can remove the 'pgcrypto' extension.
if (dbType.equals(DBMS_POSTGRES)) {
// Check if database user has permissions suitable to run a clean
if (!PostgresUtils.checkCleanPermissions(connection)) {
String username = connection.getMetaData().getUserName();
// Exit immediately, providing a descriptive error message
System.out.println("\nDatabase URL: " + connection.getMetaData().getURL());
System.out
.println("\nWARNING: ALL DATA AND TABLES IN YOUR DATABASE WILL BE PERMANENTLY DELETED.\n");
System.out.println("There is NO turning back from this action. Backup your DB before " +
"continuing.");
if (dbType.equals(DBMS_ORACLE)) {
System.out.println("\nORACLE WARNING: your RECYCLEBIN will also be PURGED.\n");
} else if (dbType.equals(DBMS_POSTGRES)) {
System.out.println(
"\nERROR: The database user '" + username + "' does not have sufficient privileges to" +
" run a 'database clean' (via Flyway).");
System.out.println(
"\nIn order to run a 'clean', the database user MUST have 'superuser' privileges");
System.out.println(
"OR the '" + PostgresUtils.PGCRYPTO + "' extension must be installed in a separate " +
"schema (see documentation).");
System.out.println(
"\nOptionally, you could also manually remove the '" + PostgresUtils.PGCRYPTO + "' " +
"extension first (DROP EXTENSION " + PostgresUtils.PGCRYPTO + " CASCADE;), then " +
"rerun the 'clean'");
System.exit(1);
"\nPOSTGRES WARNING: the '" + PostgresUtils.PGCRYPTO + "' extension will be dropped " +
"if it is in the same schema as the DSpace database.\n");
}
}
System.out.print("Do you want to PERMANENTLY DELETE everything from your database? [y/n]: ");
String choiceString = input.readLine();
input.close();
BufferedReader input = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
System.out.println("\nDatabase URL: " + connection.getMetaData().getURL());
System.out
.println("\nWARNING: ALL DATA AND TABLES IN YOUR DATABASE WILL BE PERMANENTLY DELETED.\n");
System.out.println("There is NO turning back from this action. Backup your DB before continuing.");
if (dbType.equals(DBMS_ORACLE)) {
System.out.println("\nORACLE WARNING: your RECYCLEBIN will also be PURGED.\n");
} else if (dbType.equals(DBMS_POSTGRES)) {
System.out.println(
"\nPOSTGRES WARNING: the '" + PostgresUtils.PGCRYPTO + "' extension will be dropped if it" +
" is in the same schema as the DSpace database.\n");
}
System.out.print("Do you want to PERMANENTLY DELETE everything from your database? [y/n]: ");
String choiceString = input.readLine();
input.close();
if (choiceString.equalsIgnoreCase("y")) {
System.out.println("Scrubbing database clean... (Check dspace logs for details)");
cleanDatabase(flyway, dataSource);
System.out.println("Done.");
System.exit(0);
} else {
System.out.println("No action performed.");
}
} catch (SQLException e) {
System.err.println("Clean exception:");
e.printStackTrace(System.err);
System.exit(1);
}
} else if (argv[0].equalsIgnoreCase("update-sequences")) {
try (Connection connection = dataSource.getConnection()) {
String dbType = getDbType(connection);
String sqlfile = "org/dspace/storage/rdbms/sqlmigration/" + dbType +
"/update-sequences.sql";
InputStream sqlstream = DatabaseUtils.class.getClassLoader().getResourceAsStream(sqlfile);
if (sqlstream != null) {
String s = IOUtils.toString(sqlstream, "UTF-8");
if (!s.isEmpty()) {
System.out.println("Running " + sqlfile);
connection.createStatement().execute(s);
System.out.println("update-sequences complete");
if (choiceString.equalsIgnoreCase("y")) {
System.out.println("Scrubbing database clean... (Check dspace logs for details)");
cleanDatabase(flyway, dataSource);
System.out.println("Done.");
System.exit(0);
} else {
System.err.println(sqlfile + " contains no SQL to execute");
System.out.println("No action performed.");
}
} else {
System.err.println(sqlfile + " not found");
} catch (SQLException e) {
System.err.println("Clean exception:");
e.printStackTrace(System.err);
System.exit(1);
}
}
} else {
System.out.println("\nUsage: database [action]");
System.out.println("Valid actions: 'test', 'info', 'migrate', 'repair', " +
"'update-sequences' or 'clean'");
System.out.println(
" - test = Performs a test connection to database to " +
"validate connection settings");
System.out.println(
" - info / status = Describe basic info/status about database, including validating the " +
"compatibility of this database");
System.out.println(
" - migrate = Migrate the database to the latest version");
System.out.println(
" - repair = Attempt to repair any previously failed database " +
"migrations or checksum mismatches (via Flyway repair)");
System.out.println(
" - validate = Validate current database's migration status (via Flyway validate), " +
"validating all migration checksums.");
System.out.println(
" - update-sequences = Update database sequences after running AIP ingest.");
System.out.println(
" - clean = DESTROY all data and tables in database " +
"(WARNING there is no going back!). " +
"Requires 'db.cleanDisabled=false' setting in config.");
System.out.println("");
System.exit(0);
break;
// "update-sequences" = Run DSpace's "update-sequences.sql" script
case "update-sequences":
try (Connection connection = dataSource.getConnection()) {
String dbType = getDbType(connection);
String sqlfile = "org/dspace/storage/rdbms/sqlmigration/" + dbType +
"/update-sequences.sql";
InputStream sqlstream = DatabaseUtils.class.getClassLoader().getResourceAsStream(sqlfile);
if (sqlstream != null) {
String s = IOUtils.toString(sqlstream, StandardCharsets.UTF_8);
if (!s.isEmpty()) {
System.out.println("Running " + sqlfile);
connection.createStatement().execute(s);
System.out.println("update-sequences complete");
} else {
System.err.println(sqlfile + " contains no SQL to execute");
}
} else {
System.err.println(sqlfile + " not found");
}
}
break;
// default = show help information
default:
System.out.println("\nUsage: database [action]");
System.out.println("Valid actions: 'test', 'info', 'migrate', 'repair', 'skip', " +
"'validate', 'update-sequences' or 'clean'");
System.out.println(
" - test = Performs a test connection to database to " +
"validate connection settings");
System.out.println(
" - info / status = Describe basic info/status about database, including validating the " +
"compatibility of this database");
System.out.println(
" - migrate = Migrate the database to the latest version");
System.out.println(
" - repair = Attempt to repair any previously failed database " +
"migrations or checksum mismatches (via Flyway repair)");
System.out.println(
" - skip [version] = Skip a single, pending or ignored migration, " +
"ensuring it never runs.");
System.out.println(
" - validate = Validate current database's migration status (via Flyway validate), " +
"validating all migration checksums.");
System.out.println(
" - update-sequences = Update database sequences after running AIP ingest.");
System.out.println(
" - clean = DESTROY all data and tables in database " +
"(WARNING there is no going back!). " +
"Requires 'db.cleanDisabled=false' setting in config.");
System.out.println("");
System.exit(0);
break;
}
} catch (Exception e) {
@@ -786,6 +847,89 @@ public class DatabaseUtils {
}
}
/**
* Skips the given migration by marking it as "successful" in the Flyway table. This ensures
* the given migration will never be run again.
* <P>
* WARNING: Skipping a required migration can result in unexpected errors. Make sure the migration is
* not required (or obsolete) before skipping it.
* @param dataSource current DataSource
* @param skipVersion version of migration to skip
* @throws SQLException if error occurs
*/
private static synchronized void skipMigration(DataSource dataSource,
String skipVersion) throws SQLException {
if (null == dataSource) {
throw new SQLException("The datasource is a null reference -- cannot continue.");
}
try (Connection connection = dataSource.getConnection()) {
// Setup Flyway API against our database
FluentConfiguration flywayConfiguration = setupFlyway(dataSource);
// In order to allow for skipping "Ignored" migrations, we MUST set "outOfOrder=true".
// (Otherwise Ignored migrations never appear in the pending list)
flywayConfiguration.outOfOrder(true);
// Initialized Flyway object based on this configuration
Flyway flyway = flywayConfiguration.load();
// Find the migration we are skipping in the list of pending migrations
boolean foundMigration = false;
for (MigrationInfo migration : flyway.info().pending()) {
// If this migration matches our "skipVersion"
if (migration.getVersion().equals(MigrationVersion.fromVersion(skipVersion))) {
foundMigration = true;
System.out.println("Found migration matching version='" + skipVersion + "'. " +
"Changing state to 'Success' in order to skip it.");
PreparedStatement statement = null;
try {
// Create SQL Insert which will log this migration as having already been run.
String INSERT_SQL = "INSERT INTO " + FLYWAY_TABLE + " " +
"(" +
"installed_rank, version, description, type, script, " +
"checksum, installed_by, execution_time, success" +
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
statement = connection.prepareStatement(INSERT_SQL);
// installed_rank
statement.setInt(1, getNextFlywayInstalledRank(flyway));
// version
statement.setString(2, migration.getVersion().getVersion());
// description
statement.setString(3, migration.getDescription());
// type
statement.setString(4, migration.getType().toString());
// script
statement.setString(5, migration.getScript());
// checksum
statement.setInt(6, migration.getChecksum());
// installed_by
statement.setString(7, getDBUserName(connection));
// execution_time is set to zero as we didn't really execute it
statement.setInt(8, 0);
// success=true tells Flyway this migration no longer needs to be run.
statement.setBoolean(9, true);
// Run the INSERT
statement.executeUpdate();
} finally {
if (statement != null && !statement.isClosed()) {
statement.close();
}
}
}
}
if (!foundMigration) {
System.err.println("Could not find migration to skip! " +
"No 'Pending' or 'Ignored' migrations match version='" + skipVersion + "'");
}
} catch (FlywayException fe) {
// If any FlywayException (Runtime) is thrown, change it to a SQLException
throw new SQLException("Flyway error occurred", fe);
}
}
/**
* Clean the existing database, permanently removing all data and tables
* <P>
@@ -1192,6 +1336,34 @@ public class DatabaseUtils {
return schema;
}
/**
* Get the Database User Name in use by this Connection.
*
* @param connection Current Database Connection
* @return User name as a string, or "null" if cannot be determined or unspecified
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public static String getDBUserName(Connection connection)
throws SQLException {
String username = null;
// Try to get the schema from the DB connection itself.
// As long as the Database driver supports JDBC4.1, there should be a getSchema() method
// If this method is unimplemented or doesn't exist, it will throw an exception (likely an AbstractMethodError)
try {
username = connection.getMetaData().getUserName();
} catch (Exception | AbstractMethodError e) {
// ignore
}
// If we don't know our schema, let's try the schema in the DSpace configuration
if (StringUtils.isBlank(username)) {
username = canonicalize(connection, DSpaceServicesFactory.getInstance().getConfigurationService()
.getProperty("db.username"));
}
return username;
}
/**
* Return the canonical name for a database identifier based on whether this
* database defaults to storing identifiers in uppercase or lowercase.
@@ -1443,4 +1615,22 @@ public class DatabaseUtils {
}
return null;
}
/**
* Determine next valid "installed_rank" value from Flyway, based on the "installed_rank" of the
* last applied migration.
* @param flyway currently loaded Flyway
* @return next installed rank value
*/
private static int getNextFlywayInstalledRank(Flyway flyway) throws FlywayException {
// Load all applied migrations
MigrationInfo[] appliedMigrations = flyway.info().applied();
// If no applied migrations, throw an error.
// This should never happen, but this would mean Flyway is not installed or initialized
if (ArrayUtils.isEmpty(appliedMigrations)) {
throw new FlywayException("Cannot determine next 'installed_rank' as no applied migrations exist");
}
// Find the last migration in the list, and increment its "installed_rank" by one.
return appliedMigrations[appliedMigrations.length - 1].getInstalledRank() + 1;
}
}

View File

@@ -34,6 +34,14 @@
</property>
</bean>
<bean id="DataCiteImportService"
class="org.dspace.importer.external.datacite.DataCiteImportMetadataSourceServiceImpl" scope="singleton">
<property name="metadataFieldMapping" ref="DataCiteMetadataFieldMapping"/>
</bean>
<bean id="DataCiteMetadataFieldMapping"
class="org.dspace.importer.external.datacite.DataCiteFieldMapping">
</bean>
<bean id="ArXivImportService"
class="org.dspace.importer.external.arxiv.service.ArXivImportMetadataSourceServiceImpl" scope="singleton">
<property name="metadataFieldMapping" ref="ArXivMetadataFieldMapping"/>

View File

@@ -91,4 +91,16 @@
</property>
</bean>
</beans>
<bean id="dataciteLiveImportDataProvider" class="org.dspace.external.provider.impl.LiveImportDataProvider">
<property name="metadataSource" ref="DataCiteImportService"/>
<property name="sourceIdentifier" value="datacite"/>
<property name="recordIdMetadata" value="dc.identifier.doi"/>
<property name="supportedEntityTypes">
<list>
<value>Publication</value>
<value>none</value>
</list>
</property>
</bean>
</beans>

View File

@@ -163,6 +163,8 @@ public class HttpHeadersInitializer {
}
httpHeaders.put(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS,
Collections.singletonList(HttpHeaders.ACCEPT_RANGES));
httpHeaders.put(CONTENT_DISPOSITION, Collections.singletonList(String.format(CONTENT_DISPOSITION_FORMAT,
disposition,
encodeText(fileName))));

View File

@@ -0,0 +1,149 @@
/**
* 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;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.el.MethodNotFoundException;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.impl.client.CloseableHttpClient;
import org.dspace.builder.CollectionBuilder;
import org.dspace.builder.CommunityBuilder;
import org.dspace.builder.ItemBuilder;
import org.dspace.content.Item;
import org.dspace.importer.external.datacite.DataCiteImportMetadataSourceServiceImpl;
import org.dspace.importer.external.datamodel.ImportRecord;
import org.dspace.importer.external.liveimportclient.service.LiveImportClientImpl;
import org.dspace.importer.external.metadatamapping.MetadatumDTO;
import org.junit.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Integration tests for {@link DataCiteImportMetadataSourceServiceImpl}
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com)
*/
public class DataCiteImportMetadataSourceServiceIT extends AbstractLiveImportIntegrationTest {
@Autowired
private LiveImportClientImpl liveImportClientImpl;
@Autowired
private DataCiteImportMetadataSourceServiceImpl dataCiteServiceImpl;
@Test
public void dataCiteImportMetadataGetRecordsTest() throws Exception {
context.turnOffAuthorisationSystem();
CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient();
CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class);
try (InputStream dataCiteResp = getClass().getResourceAsStream("dataCite-test.json")) {
String dataCiteRespXmlResp = IOUtils.toString(dataCiteResp, Charset.defaultCharset());
liveImportClientImpl.setHttpClient(httpClient);
CloseableHttpResponse response = mockResponse(dataCiteRespXmlResp, 200, "OK");
when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response);
context.restoreAuthSystemState();
ArrayList<ImportRecord> collection2match = getRecords();
Collection<ImportRecord> recordsImported = dataCiteServiceImpl.getRecords("10.48550/arxiv.2207.04779",
0, -1);
assertEquals(1, recordsImported.size());
matchRecords(new ArrayList<>(recordsImported), collection2match);
} finally {
liveImportClientImpl.setHttpClient(originalHttpClient);
}
}
@Test
public void dataCiteImportMetadataGetRecordsCountTest() throws Exception {
context.turnOffAuthorisationSystem();
CloseableHttpClient originalHttpClient = liveImportClientImpl.getHttpClient();
CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class);
try (InputStream dataciteResp = getClass().getResourceAsStream("dataCite-test.json")) {
String dataciteTextResp = IOUtils.toString(dataciteResp, Charset.defaultCharset());
liveImportClientImpl.setHttpClient(httpClient);
CloseableHttpResponse response = mockResponse(dataciteTextResp, 200, "OK");
when(httpClient.execute(ArgumentMatchers.any())).thenReturn(response);
context.restoreAuthSystemState();
int tot = dataCiteServiceImpl.getRecordsCount("10.48550/arxiv.2207.04779");
assertEquals(1, tot);
} finally {
liveImportClientImpl.setHttpClient(originalHttpClient);
}
}
@Test(expected = MethodNotFoundException.class)
public void dataCiteImportMetadataFindMatchingRecordsTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
org.dspace.content.Collection col1 = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Collection 1")
.build();
Item testItem = ItemBuilder.createItem(context, col1)
.withTitle("test item")
.withIssueDate("2021")
.build();
context.restoreAuthSystemState();
dataCiteServiceImpl.findMatchingRecords(testItem);
}
private ArrayList<ImportRecord> getRecords() {
ArrayList<ImportRecord> records = new ArrayList<>();
//define first record
List<MetadatumDTO> metadatums = new ArrayList<>();
MetadatumDTO title = createMetadatumDTO("dc", "title", null,
"Mathematical Proof Between Generations");
MetadatumDTO doi = createMetadatumDTO("dc", "identifier", "doi", "10.48550/arxiv.2207.04779");
MetadatumDTO author1 = createMetadatumDTO("dc", "contributor", "author", "Bayer, Jonas");
MetadatumDTO author2 = createMetadatumDTO("dc", "contributor", "author", "Benzmüller, Christoph");
MetadatumDTO author3 = createMetadatumDTO("dc", "contributor", "author", "Buzzard, Kevin");
MetadatumDTO author4 = createMetadatumDTO("dc", "contributor", "author", "David, Marco");
MetadatumDTO author5 = createMetadatumDTO("dc", "contributor", "author", "Lamport, Leslie");
MetadatumDTO author6 = createMetadatumDTO("dc", "contributor", "author", "Matiyasevich, Yuri");
MetadatumDTO author7 = createMetadatumDTO("dc", "contributor", "author", "Paulson, Lawrence");
MetadatumDTO author8 = createMetadatumDTO("dc", "contributor", "author", "Schleicher, Dierk");
MetadatumDTO author9 = createMetadatumDTO("dc", "contributor", "author", "Stock, Benedikt");
MetadatumDTO author10 = createMetadatumDTO("dc", "contributor", "author", "Zelmanov, Efim");
metadatums.add(title);
metadatums.add(doi);
metadatums.add(author1);
metadatums.add(author2);
metadatums.add(author3);
metadatums.add(author4);
metadatums.add(author5);
metadatums.add(author6);
metadatums.add(author7);
metadatums.add(author8);
metadatums.add(author9);
metadatums.add(author10);
ImportRecord firstRecord = new ImportRecord(metadatums);
records.add(firstRecord);
return records;
}
}

View File

@@ -53,7 +53,7 @@ public class ExternalSourcesRestControllerIT extends AbstractControllerIntegrati
ExternalSourceMatcher.matchExternalSource(
"openAIREFunding", "openAIREFunding", false)
)))
.andExpect(jsonPath("$.page.totalElements", Matchers.is(9)));
.andExpect(jsonPath("$.page.totalElements", Matchers.is(10)));
}
@Test

File diff suppressed because one or more lines are too long

View File

@@ -85,4 +85,10 @@ scopus.search-api.viewMode =
wos.apiKey =
wos.url = https://wos-api.clarivate.com/api/wos/id/
wos.url.search = https://wos-api.clarivate.com/api/wos/?databaseId=WOS&lang=en&usrQuery=
##################################################################
#################################################################
#------------------------- DataCite ----------------------------#
#---------------------------------------------------------------#
datacite.url = https://api.datacite.org/dois/
datacite.timeout = 180000
#################################################################

View File

@@ -187,6 +187,7 @@
<internal>false</internal>
<extension>jpeg</extension>
<extension>jpg</extension>
<extension>jfif</extension>
</bitstream-type>
<bitstream-type>

View File

@@ -0,0 +1,62 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
default-autowire-candidates="*Service,*DAO,javax.sql.DataSource">
<context:annotation-config/>
<!-- allows us to use spring annotations in beans -->
<util:map id="dataciteMetadataFieldMap" key-type="org.dspace.importer.external.metadatamapping.MetadataFieldConfig"
value-type="org.dspace.importer.external.metadatamapping.contributor.MetadataContributor">
<description>Defines which metadatum is mapped on which metadatum. Note that while the key must be unique it
only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over
what metadatafield is generated.
</description>
<entry key-ref="datacite.title" value-ref="dataciteTitleContrib"/>
<entry key-ref="datacite.id" value-ref="dataciteIDContrib"/>
<entry key-ref="datacite.author" value-ref="dataciteAuthorContrib"/>
<!-- TODO: Further Mappings here! querys are applied among the data.attributes object containing the informations -->
</util:map>
<bean id="dataciteAuthorContrib" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="metadataProcessor">
<bean class="org.dspace.importer.external.metadatamapping.contributor.ArrayElementAttributeProcessor">
<property name="pathToArray" value="/creators"/>
<property name="elementAttribute" value="/name"/>
</bean>
</property>
<property name="field" ref="datacite.author"/>
</bean>
<bean id="datacite.author" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.contributor.author"/>
</bean>
<bean id="dataciteTitleContrib" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="metadataProcessor">
<bean class="org.dspace.importer.external.metadatamapping.contributor.ArrayElementAttributeProcessor">
<property name="pathToArray" value="/titles"/>
<property name="elementAttribute" value="/title"/>
</bean>
</property>
<property name="field" ref="datacite.title"/>
</bean>
<bean id="datacite.title" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.title"/>
</bean>
<!-- must be present to be imported, since it's used as the recordId-->
<bean id="dataciteIDContrib" class="org.dspace.importer.external.metadatamapping.contributor.SimpleJsonPathMetadataContributor">
<property name="field" ref="datacite.id"/>
<property name="query" value="/doi"/>
</bean>
<bean id="datacite.id" class="org.dspace.importer.external.metadatamapping.MetadataFieldConfig">
<constructor-arg value="dc.identifier.doi"/>
</bean>
</beans>

View File

@@ -247,6 +247,18 @@
<property name="field" value="dc.title"/>
<property name="snippets" value="5"/>
</bean>
<bean class="org.dspace.discovery.configuration.DiscoveryHitHighlightFieldConfiguration">
<property name="field" value="organization.legalName"/>
<property name="snippets" value="5"/>
</bean>
<bean class="org.dspace.discovery.configuration.DiscoveryHitHighlightFieldConfiguration">
<property name="field" value="person.givenName"/>
<property name="snippets" value="5"/>
</bean>
<bean class="org.dspace.discovery.configuration.DiscoveryHitHighlightFieldConfiguration">
<property name="field" value="person.familyName"/>
<property name="snippets" value="5"/>
</bean>
<!-- By default, full text snippets are disabled, as snippets of embargoed/restricted bitstreams
may appear in search results when the Item is public. See DS-3498
<bean class="org.dspace.discovery.configuration.DiscoveryHitHighlightFieldConfiguration">

View File

@@ -212,4 +212,15 @@
</property>
</bean>
</beans>
<bean id="dataciteLiveImportDataProvider" class="org.dspace.external.provider.impl.LiveImportDataProvider">
<property name="metadataSource" ref="DataCiteImportService"/>
<property name="sourceIdentifier" value="datacite"/>
<property name="recordIdMetadata" value="dc.identifier.doi"/>
<property name="supportedEntityTypes">
<list>
<value>Publication</value>
<value>none</value>
</list>
</property>
</bean>
</beans>