diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml index 2890061b4d24..487f246ae360 100644 --- a/bitbucket-pipelines.yml +++ b/bitbucket-pipelines.yml @@ -2,7 +2,7 @@ image: maven:3.6.1 definitions: docker: - memory: 2048 # increase memory for docker-in-docker from 1GB to 2GB + memory: 4096 # increase memory for docker-in-docker from 1GB to 4GB caches: gradlewrapper: ~/.gradle/wrapper steps: @@ -46,9 +46,40 @@ definitions: script: - export MAVEN_OPTS="-Xmx4096M" - ./dspace-api/src/test/data/dspaceFolder/bin/install_grobid.sh - - mvn clean install license:check -DskipUnitTests=true -Pdspace-rest -DskipITs=false -Pdspace-rest -DskipIntegrationTests=false -P !assembly -B -V -Dsurefire.rerunFailingTestsCount=2 -Dtest.argLine=-Xmx4096M + - mvn clean install -DskipUnitTests=true -Pdspace-rest -DskipITs=true -DskipIntegrationTests=true -P !assembly -B -V + - mvn test -pl dspace-iiif,dspace-oai,dspace-rdf,dspace-rest,dspace-server-webapp,dspace-services,dspace-sword,dspace-swordv2 -Dmaven.test.skip=false -DskipUnitTests=false -Pdspace-rest -DskipITs=false -Pdspace-rest -DskipIntegrationTests=false -B -V -Dsurefire.rerunFailingTestsCount=2 -Dtest.argLine=-Xmx4096M -DfailIfNoTests=false -Dtest=*IT,!Abstract*,!ItemImportIT,!GenericAuthorizationFeatureIT,!ItemRestRepositoryIT,!LeftTiltedRelationshipRestRepositoryIT,!RelationshipRestRepositoryIT,!StatisticsRestRepositoryIT,!WorkspaceItemRestRepositoryIT,!DiscoveryRestControllerIT,!PatchMetadataIT,!RightTiltedRelationshipRestRepositoryIT + artifacts: + - dspace-iiif/target/surefire-reports/*-output.txt + - dspace-oai/target/surefire-reports/*-output.txt + - dspace-rdf/target/surefire-reports/*-output.txt + - dspace-rest/target/surefire-reports/*-output.txt + - dspace-server-webapp/target/surefire-reports/*-output.txt + - dspace-services/target/surefire-reports/*-output.txt + - dspace-sword/target/surefire-reports/*-output.txt + - dspace-swordv2/target/surefire-reports/*-output.txt + - dspace-iiif/target/failsafe-reports/*-output.txt + - dspace-oai/target/failsafe-reports/*-output.txt + - dspace-rdf/target/failsafe-reports/*-output.txt + - dspace-rest/target/failsafe-reports/*-output.txt + - dspace-server-webapp/target/failsafe-reports/*-output.txt + - dspace-services/target/failsafe-reports/*-output.txt + - dspace-sword/target/failsafe-reports/*-output.txt + - dspace-swordv2/target/failsafe-reports/*-output.txt + + - step: &integration-tests-slow + runs-on: self.hosted + name: integration-tests-slow + size: 4x + caches: + - gradle + - gradlewrapper + - maven + script: + - export MAVEN_OPTS="-Xmx4096M" + - ./dspace-api/src/test/data/dspaceFolder/bin/install_grobid.sh + - mvn clean install -DskipUnitTests=true -Pdspace-rest -DskipITs=true -DskipIntegrationTests=true -P !assembly -B -V + - mvn test -Dmaven.test.skip=false -DskipUnitTests=false -Pdspace-rest -DskipITs=false -Pdspace-rest -DskipIntegrationTests=false -B -V -Dsurefire.rerunFailingTestsCount=2 -Dtest.argLine=-Xmx4096M -DfailIfNoTests=false -Dtest=ItemImportIT,GenericAuthorizationFeatureIT,ItemRestRepositoryIT,LeftTiltedRelationshipRestRepositoryIT,RelationshipRestRepositoryIT,StatisticsRestRepositoryIT,WorkspaceItemRestRepositoryIT,DiscoveryRestControllerIT,PatchMetadataIT,RightTiltedRelationshipRestRepositoryIT artifacts: - - dspace-api/target/surefire-reports/*-output.txt - dspace-iiif/target/surefire-reports/*-output.txt - dspace-oai/target/surefire-reports/*-output.txt - dspace-rdf/target/surefire-reports/*-output.txt @@ -57,7 +88,6 @@ definitions: - dspace-services/target/surefire-reports/*-output.txt - dspace-sword/target/surefire-reports/*-output.txt - dspace-swordv2/target/surefire-reports/*-output.txt - - dspace-api/target/failsafe-reports/*-output.txt - dspace-iiif/target/failsafe-reports/*-output.txt - dspace-oai/target/failsafe-reports/*-output.txt - dspace-rdf/target/failsafe-reports/*-output.txt @@ -67,21 +97,51 @@ definitions: - dspace-sword/target/failsafe-reports/*-output.txt - dspace-swordv2/target/failsafe-reports/*-output.txt + - step: &integration-tests-dspace-api + runs-on: self.hosted + name: integration-tests-dspace-api + size: 4x + caches: + - gradle + - gradlewrapper + - maven + script: + - export MAVEN_OPTS="-Xmx4096M" + - mvn clean install license:check -DskipUnitTests=true -Pdspace-rest -DskipITs=true -DskipIntegrationTests=true -P !assembly -B -V + - mvn verify -Dmaven.test.skip=false -pl dspace-api -DskipUnitTests=true -Pdspace-rest -DskipITs=false -Pdspace-rest -DskipIntegrationTests=false -P !assembly -B -V -Dsurefire.rerunFailingTestsCount=2 -Dtest.argLine=-Xmx4096M + artifacts: + - dspace-api/target/surefire-reports/*-output.txt + - dspace-api/target/failsafe-reports/*-output.txt + pipelines: branches: 'main-cris': - parallel: - step: *unit-test-code-checks - step: *integration-tests + - step: *integration-tests-slow + - step: *integration-tests-dspace-api + 'dspace-cris-2023_02_x': + - parallel: + - step: *unit-test-code-checks + - step: *integration-tests + - step: *integration-tests-slow + - step: *integration-tests-dspace-api pull-requests: '**': - parallel: - step: *unit-test-code-checks - step: *integration-tests + - step: *integration-tests-slow + - step: *integration-tests-dspace-api custom: integration: - step: *integration-tests + integration-slow: + - step: *integration-tests-slow + integration-dspace-api: + - step: *integration-tests-dspace-api unit: - step: *unit-test-code-checks diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index c801b8cac949..88f0e7e21ac9 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -1981,7 +1981,9 @@ public List getMetadata(Item item, String schema, String element, List dbMetadataValues = item.getMetadata(); List fullMetadataValueList = new LinkedList<>(); - fullMetadataValueList.addAll(relationshipMetadataService.getRelationshipMetadata(item, true)); + if (configurationService.getBooleanProperty("item.enable-virtual-metadata", false)) { + fullMetadataValueList.addAll(relationshipMetadataService.getRelationshipMetadata(item, true)); + } fullMetadataValueList.addAll(dbMetadataValues); item.setCachedMetadata(MetadataValueComparators.sort(fullMetadataValueList)); diff --git a/dspace-api/src/main/java/org/dspace/content/dao/ItemForMetadataEnhancementUpdateDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/ItemForMetadataEnhancementUpdateDAO.java new file mode 100644 index 000000000000..532e925a965e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/dao/ItemForMetadataEnhancementUpdateDAO.java @@ -0,0 +1,49 @@ +/** + * 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.dao; + +import java.sql.SQLException; +import java.util.UUID; + +import org.dspace.core.Context; + +public interface ItemForMetadataEnhancementUpdateDAO { + + /** + * Add to the metadata_enhancement_update table queue an entry of each items + * that is potentially affected by the update of the Item with the specified + * uuid. The items potentially affected are the one that have the provided uuid + * as value of a cris.virtualsource.* metadata + * + * @param context the DSpace Context object + * @param uuid the uuid of the updated item + * @return the number of affected items scheduled for update + * @throws SQLException if a problem with the database occurs + */ + int saveAffectedItemsForUpdate(Context context, UUID uuid); + + /** + * Remove from the metadata_enhancement_update table queue the entry if any + * related to the specified id in older than the current date + * + * @param context the DSpace Context object + * @param itemToRemove the uuid of the processed item + * @throws SQLException if a problem with the database occurs + */ + void removeItemForUpdate(Context context, UUID itemToRemove); + + /** + * Extract and remove from the table the first uuid to process from the + * itemupdate_metadata_enhancement table ordered by date queued asc (older + * first) + * + * @param context + * @return + */ + UUID pollItemToUpdate(Context context); +} diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemForMetadataEnhancementUpdateDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemForMetadataEnhancementUpdateDAOImpl.java new file mode 100644 index 000000000000..8ffe2d5de534 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/ItemForMetadataEnhancementUpdateDAOImpl.java @@ -0,0 +1,152 @@ +/** + * 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.dao.impl; + +import java.sql.SQLException; +import java.util.UUID; + +import org.dspace.content.MetadataSchema; +import org.dspace.content.dao.ItemForMetadataEnhancementUpdateDAO; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.MetadataSchemaService; +import org.dspace.core.Context; +import org.dspace.core.DBConnection; +import org.dspace.services.ConfigurationService; +import org.dspace.utils.DSpace; +import org.hibernate.Session; +import org.hibernate.query.NativeQuery; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Hibernate implementation of the Database Access Object interface class for + * the ItemForMetadataEnhancementUpdate object. This class is responsible for + * all database calls for the ItemForMetadataEnhancementUpdate object and is + * autowired by spring This class should never be accessed directly. + */ +public class ItemForMetadataEnhancementUpdateDAOImpl implements ItemForMetadataEnhancementUpdateDAO { + @Autowired + ConfigurationService configurationService; + + @Override + public void removeItemForUpdate(Context context, UUID itemToRemove) { + try { + Session session = getHibernateSession(); + String sql = "DELETE FROM itemupdate_metadata_enhancement WHERE uuid = :uuid"; + NativeQuery query = session.createNativeQuery(sql); + query.setParameter("uuid", itemToRemove); + query.executeUpdate(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + @Override + public UUID pollItemToUpdate(Context context) { + try { + Session session = getHibernateSession(); + String sql = "SELECT cast(uuid as varchar) FROM itemupdate_metadata_enhancement" + + " ORDER BY date_queued ASC LIMIT 1"; + NativeQuery query = session.createNativeQuery(sql); + Object uuidObj = query.uniqueResult(); + if (uuidObj != null) { + UUID uuid; + if (uuidObj instanceof String) { + uuid = (UUID) UUID.fromString((String) uuidObj); + } else { + throw new RuntimeException("Unexpected result type from the database " + uuidObj); + } + removeItemForUpdate(context, uuid); + return uuid; + } else { + return null; + } + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + @Override + public int saveAffectedItemsForUpdate(Context context, UUID uuid) { + try { + Session session = getHibernateSession(); + MetadataSchemaService schemaService = ContentServiceFactory.getInstance().getMetadataSchemaService(); + MetadataSchema schema = schemaService.find(context, "cris"); + String sqlInsertOrUpdate; + if ("org.h2.Driver".equals(configurationService.getProperty("db.driver"))) { + // H2 doesn't support the INSERT OR UPDATE statement so let's do in two steps + // update queued date for records already in the queue + String sqlUpdate = "UPDATE itemupdate_metadata_enhancement" + + " SET date_queued = CURRENT_TIMESTAMP" + + " WHERE uuid IN (" + + " SELECT dspace_object_id FROM metadatavalue " + " WHERE metadata_field_id IN" + + " ( SELECT metadata_field_id FROM metadatafieldregistry " + + " WHERE metadata_schema_id = :schema AND element = 'virtualsource')" + + " AND SUBSTRING(text_value,1,36) = :uuid)"; + String sqlInsert = + "INSERT INTO itemupdate_metadata_enhancement (uuid, date_queued)" + + " SELECT DISTINCT dspace_object_id, CURRENT_TIMESTAMP FROM metadatavalue " + + " WHERE metadata_field_id IN" + + " ( SELECT metadata_field_id FROM metadatafieldregistry " + + " WHERE metadata_schema_id = :schema AND element = 'virtualsource')" + + " AND SUBSTRING(text_value,1,36) = :uuid " + + " AND dspace_object_id NOT IN (" + + " SELECT uuid" + + " FROM itemupdate_metadata_enhancement" + + " )"; + NativeQuery queryUpdate = session.createNativeQuery(sqlUpdate); + queryUpdate.setParameter("uuid", uuid.toString()); + queryUpdate.setParameter("schema", schema.getID()); + queryUpdate.executeUpdate(); + NativeQuery queryInsert = session.createNativeQuery(sqlInsert); + queryInsert.setParameter("uuid", uuid.toString()); + queryInsert.setParameter("schema", schema.getID()); + return queryInsert.executeUpdate(); + } else { + sqlInsertOrUpdate = "INSERT INTO itemupdate_metadata_enhancement (uuid, date_queued) " + + " SELECT DISTINCT dspace_object_id, CURRENT_TIMESTAMP FROM metadatavalue " + + " WHERE metadata_field_id IN" + + " ( SELECT metadata_field_id FROM metadatafieldregistry " + + " WHERE metadata_schema_id = :schema AND element = 'virtualsource')" + + " AND substring(text_value,1,36) = :uuid " + + " ON CONFLICT (uuid) DO UPDATE" + + " SET date_queued = EXCLUDED.date_queued"; + NativeQuery queryInsertOrUpdate = session.createNativeQuery(sqlInsertOrUpdate); + queryInsertOrUpdate.setParameter("uuid", uuid.toString()); + queryInsertOrUpdate.setParameter("schema", schema.getID()); + return queryInsertOrUpdate.executeUpdate(); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * The Hibernate Session used in the current thread + * + * @return the current Session. + * @throws SQLException + */ + private Session getHibernateSession() throws SQLException { + DBConnection dbConnection = new DSpace().getServiceManager().getServiceByName(null, DBConnection.class); + return ((Session) dbConnection.getSession()); + } + + public UUID ConvertByteArrayToUUID(byte[] bytea) { + long mostSigBits = 0; + long leastSigBits = 0; + for (int i = 0; i < 8; i++) { + mostSigBits = (mostSigBits << 8) | (bytea[i] & 0xff); + leastSigBits = (leastSigBits << 8) | (bytea[i + 8] & 0xff); + } + + UUID uuid = new UUID(mostSigBits, leastSigBits); + return uuid; + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/content/enhancer/consumer/ItemEnhancerConsumer.java b/dspace-api/src/main/java/org/dspace/content/enhancer/consumer/ItemEnhancerConsumer.java index c526537bf5ac..ebac7a82e0ad 100644 --- a/dspace-api/src/main/java/org/dspace/content/enhancer/consumer/ItemEnhancerConsumer.java +++ b/dspace-api/src/main/java/org/dspace/content/enhancer/consumer/ItemEnhancerConsumer.java @@ -9,9 +9,12 @@ import java.util.HashSet; import java.util.Set; +import java.util.UUID; import org.dspace.content.Item; import org.dspace.content.enhancer.service.ItemEnhancerService; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.event.Consumer; import org.dspace.event.Event; @@ -29,10 +32,12 @@ public class ItemEnhancerConsumer implements Consumer { public static final String ITEMENHANCER_ENABLED = "itemenhancer.enabled"; - private Set itemsAlreadyProcessed = new HashSet(); + private Set itemsToProcess = new HashSet(); private ItemEnhancerService itemEnhancerService; + private ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); @Override @@ -53,19 +58,11 @@ public void consume(Context context, Event event) throws Exception { } Item item = (Item) event.getSubject(context); - if (item == null || itemsAlreadyProcessed.contains(item) || !item.isArchived()) { + if (item == null || !item.isArchived()) { return; } - itemsAlreadyProcessed.add(item); - - context.turnOffAuthorisationSystem(); - try { - itemEnhancerService.enhance(context, item, false); - } finally { - context.restoreAuthSystemState(); - } - + itemsToProcess.add(item.getID()); } protected boolean isConsumerEnabled() { @@ -74,7 +71,19 @@ protected boolean isConsumerEnabled() { @Override public void end(Context ctx) throws Exception { - itemsAlreadyProcessed.clear(); + ctx.turnOffAuthorisationSystem(); + try { + for (UUID uuid : itemsToProcess) { + Item item = itemService.find(ctx, uuid); + if (item != null) { + itemEnhancerService.enhance(ctx, item, false); + itemEnhancerService.saveAffectedItemsForUpdate(ctx, item.getID()); + } + } + } finally { + ctx.restoreAuthSystemState(); + } + itemsToProcess.clear(); } } diff --git a/dspace-api/src/main/java/org/dspace/content/enhancer/impl/RelatedEntityItemEnhancer.java b/dspace-api/src/main/java/org/dspace/content/enhancer/impl/RelatedEntityItemEnhancer.java index 7a6f2927092c..870dfbddfa8e 100644 --- a/dspace-api/src/main/java/org/dspace/content/enhancer/impl/RelatedEntityItemEnhancer.java +++ b/dspace-api/src/main/java/org/dspace/content/enhancer/impl/RelatedEntityItemEnhancer.java @@ -21,6 +21,7 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import org.dspace.authority.service.AuthorityValueService; import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.authority.Choices; @@ -49,10 +50,13 @@ public class RelatedEntityItemEnhancer extends AbstractItemEnhancer { @Autowired private ItemService itemService; + @Autowired + private RelatedEntityItemEnhancerUtils relatedEntityItemEnhancerUtils; + /** - * the entity that can be extended by this enhancer, i.e. Publication + * the entities that can be extended by this enhancer, i.e. Publication */ - private String sourceEntityType; + private List sourceEntityTypes; /** * the metadata used to navigate the relation, i.e. dc.contributor.author @@ -66,7 +70,7 @@ public class RelatedEntityItemEnhancer extends AbstractItemEnhancer { @Override public boolean canEnhance(Context context, Item item) { - return sourceEntityType == null || sourceEntityType.equals(itemService.getEntityTypeLabel(item)); + return sourceEntityTypes == null || sourceEntityTypes.contains(itemService.getEntityTypeLabel(item)); } @Override @@ -81,7 +85,8 @@ public boolean enhance(Context context, Item item, boolean deepMode) { throw new SQLRuntimeException(e); } } else { - Map> currMetadataValues = getCurrentVirtualsMap(item); + Map> currMetadataValues = relatedEntityItemEnhancerUtils + .getCurrentVirtualsMap(item, getVirtualQualifier()); Map> toBeMetadataValues = getToBeVirtualMetadata(context, item); if (!equivalent(currMetadataValues, toBeMetadataValues)) { try { @@ -106,8 +111,8 @@ private void clearAllVirtualMetadata(Context context, Item item) throws SQLExcep private void addMetadata(Context context, Item item, Map> toBeMetadataValues) throws SQLException { for (Entry> metadataValues : toBeMetadataValues.entrySet()) { - addVirtualSourceField(context, item, metadataValues.getKey()); for (MetadataValueDTO dto : metadataValues.getValue()) { + addVirtualSourceField(context, item, metadataValues.getKey()); addVirtualField(context, item, dto.getValue(), dto.getAuthority(), dto.getLanguage(), dto.getConfidence()); } @@ -212,7 +217,8 @@ private boolean cleanObsoleteVirtualFields(Context context, Item item) throws SQ private List getObsoleteVirtualFields(Item item) { List obsoleteVirtualFields = new ArrayList<>(); - Map> currentVirtualsMap = getCurrentVirtualsMap(item); + Map> currentVirtualsMap = relatedEntityItemEnhancerUtils + .getCurrentVirtualsMap(item, getVirtualQualifier()); Set virtualSources = getVirtualSources(item); for (String authority : currentVirtualsMap.keySet()) { if (!virtualSources.contains(authority)) { @@ -235,41 +241,6 @@ private Set getVirtualSources(Item item) { .collect(Collectors.toSet()); } - private Map> getCurrentVirtualsMap(Item item) { - Map> currentVirtualsMap = new HashMap>(); - List sources = itemService.getMetadata(item, VIRTUAL_METADATA_SCHEMA, - VIRTUAL_SOURCE_METADATA_ELEMENT, getVirtualQualifier(), Item.ANY); - List generated = itemService.getMetadata(item, VIRTUAL_METADATA_SCHEMA, VIRTUAL_METADATA_ELEMENT, - getVirtualQualifier(), Item.ANY); - - if (sources.size() != generated.size()) { - LOGGER.error( - "inconsistent virtual metadata for the item {} got {} sources and {} generated virtual metadata", - item.getID().toString(), sources.size(), generated.size()); - } - - for (int i = 0; i < Integer.max(sources.size(), generated.size()); i++) { - String authority; - if (i < sources.size()) { - authority = sources.get(i).getValue(); - } else { - // we have less source than virtual metadata let's generate a random uuid to - // associate with these extra metadata so that they will be managed as obsolete - // value - authority = UUID.randomUUID().toString(); - } - List mvalues = currentVirtualsMap.get(authority); - if (mvalues == null) { - mvalues = new ArrayList(); - } - if (i < generated.size()) { - mvalues.add(generated.get(i)); - } - currentVirtualsMap.put(authority, mvalues); - } - return currentVirtualsMap; - } - private Optional getRelatedVirtualField(Item item, int pos) { return getVirtualFields(item).stream() .skip(pos) @@ -278,7 +249,8 @@ private Optional getRelatedVirtualField(Item item, int pos) { private boolean performEnhancement(Context context, Item item) throws SQLException { boolean result = false; - Map> currentVirtualsMap = getCurrentVirtualsMap(item); + Map> currentVirtualsMap = relatedEntityItemEnhancerUtils + .getCurrentVirtualsMap(item, getVirtualQualifier()); Set virtualSources = getVirtualSources(item); for (String authority : virtualSources) { boolean foundAtLeastOne = false; @@ -336,8 +308,14 @@ private List getVirtualFields(Item item) { private void addVirtualField(Context context, Item item, String value, String authority, String lang, int confidence) throws SQLException { - itemService.addMetadata(context, item, VIRTUAL_METADATA_SCHEMA, VIRTUAL_METADATA_ELEMENT, getVirtualQualifier(), - lang, value, authority, confidence); + if (StringUtils.startsWith(authority, AuthorityValueService.GENERATE) + || StringUtils.startsWith(authority, AuthorityValueService.REFERENCE)) { + itemService.addMetadata(context, item, VIRTUAL_METADATA_SCHEMA, VIRTUAL_METADATA_ELEMENT, + getVirtualQualifier(), lang, value, null, Choices.CF_UNSET); + } else { + itemService.addMetadata(context, item, VIRTUAL_METADATA_SCHEMA, VIRTUAL_METADATA_ELEMENT, + getVirtualQualifier(), lang, value, authority, confidence); + } } private void addVirtualSourceField(Context context, Item item, String sourceValueAuthority) throws SQLException { @@ -345,24 +323,8 @@ private void addVirtualSourceField(Context context, Item item, String sourceValu getVirtualQualifier(), null, sourceValueAuthority); } - public void setSourceEntityType(String sourceEntityType) { - this.sourceEntityType = sourceEntityType; - } - - @Deprecated - public void setSourceItemMetadataField(String sourceItemMetadataField) { - LOGGER.warn( - "RelatedEntityItemEnhancer configured using the old single source item metadata field, " - + "please update the configuration to use the list"); - this.sourceItemMetadataFields = List.of(sourceItemMetadataField); - } - - @Deprecated - public void setRelatedItemMetadataField(String relatedItemMetadataField) { - LOGGER.warn( - "RelatedEntityItemEnhancer configured using the old single related item metadata field, " - + "please update the configuration to use the list"); - this.relatedItemMetadataFields = List.of(relatedItemMetadataField); + public void setSourceEntityTypes(List sourceEntityTypes) { + this.sourceEntityTypes = sourceEntityTypes; } public void setRelatedItemMetadataFields(List relatedItemMetadataFields) { diff --git a/dspace-api/src/main/java/org/dspace/content/enhancer/impl/RelatedEntityItemEnhancerUtils.java b/dspace-api/src/main/java/org/dspace/content/enhancer/impl/RelatedEntityItemEnhancerUtils.java new file mode 100644 index 000000000000..064223b56422 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/enhancer/impl/RelatedEntityItemEnhancerUtils.java @@ -0,0 +1,72 @@ +/** + * 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.enhancer.impl; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.service.ItemService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Utility methods used by {@link RelatedEntityItemEnhancer} + * + * @author Andrea Bollini (andrea.bollini at 4science.com) + * + */ +public class RelatedEntityItemEnhancerUtils { + + @Autowired + private ItemService itemService; + + private static final Logger LOGGER = LoggerFactory.getLogger(RelatedEntityItemEnhancerUtils.class); + + public Map> getCurrentVirtualsMap(Item item, String virtualQualifier) { + Map> currentVirtualsMap = new HashMap>(); + List sources = itemService.getMetadata(item, RelatedEntityItemEnhancer.VIRTUAL_METADATA_SCHEMA, + RelatedEntityItemEnhancer.VIRTUAL_SOURCE_METADATA_ELEMENT, virtualQualifier, Item.ANY); + List generated = itemService.getMetadata(item, + RelatedEntityItemEnhancer.VIRTUAL_METADATA_SCHEMA, RelatedEntityItemEnhancer.VIRTUAL_METADATA_ELEMENT, + virtualQualifier, Item.ANY); + + if (sources.size() != generated.size()) { + LOGGER.error( + "inconsistent virtual metadata for the item {} got {} sources and {} generated virtual metadata", + item.getID().toString(), sources.size(), generated.size()); + } + + for (int i = 0; i < Integer.max(sources.size(), generated.size()); i++) { + String authority; + if (i < sources.size()) { + authority = sources.get(i).getValue(); + } else { + // we have less source than virtual metadata let's generate a random uuid to + // associate with these extra metadata so that they will be managed as obsolete + // value + authority = UUID.randomUUID().toString(); + } + List mvalues = currentVirtualsMap.get(authority); + if (mvalues == null) { + mvalues = new ArrayList(); + } + if (i < generated.size()) { + mvalues.add(generated.get(i)); + } + currentVirtualsMap.put(authority, mvalues); + } + return currentVirtualsMap; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/content/enhancer/script/ItemEnhancerScript.java b/dspace-api/src/main/java/org/dspace/content/enhancer/script/ItemEnhancerScript.java index 3100920dc17c..c965edd6766d 100644 --- a/dspace-api/src/main/java/org/dspace/content/enhancer/script/ItemEnhancerScript.java +++ b/dspace-api/src/main/java/org/dspace/content/enhancer/script/ItemEnhancerScript.java @@ -31,6 +31,7 @@ * */ public class ItemEnhancerScript extends DSpaceRunnable> { + private final int PAGE_SIZE = 20; private ItemService itemService; @@ -57,7 +58,7 @@ public void internalRun() throws Exception { context.turnOffAuthorisationSystem(); try { - enhanceItems(); + enhanceItems(context); context.complete(); handler.logInfo("Enhancement completed with success"); } catch (Exception e) { @@ -68,20 +69,28 @@ public void internalRun() throws Exception { } } - private void enhanceItems() { - findItemsToEnhance().forEachRemaining(this::enhanceItem); + private void enhanceItems(Context context) { + try { + int total = itemService.countArchivedItems(context); + for (int offset = 0; offset < total; offset += PAGE_SIZE) { + findItemsToEnhance(offset).forEachRemaining(this::enhanceItem); + context.commit(); + context.clear(); + } + } catch (SQLException e) { + throw new SQLRuntimeException(e); + } } - private Iterator findItemsToEnhance() { + private Iterator findItemsToEnhance(int offset) { try { - return itemService.findAll(context); + return itemService.findAll(context, PAGE_SIZE, offset); } catch (SQLException e) { throw new SQLRuntimeException(e); } } private void enhanceItem(Item item) { - itemEnhancerService.enhance(context, item, force); uncacheItem(item); } diff --git a/dspace-api/src/main/java/org/dspace/content/enhancer/service/ItemEnhancerService.java b/dspace-api/src/main/java/org/dspace/content/enhancer/service/ItemEnhancerService.java index 08170448e681..d7f7318249b2 100644 --- a/dspace-api/src/main/java/org/dspace/content/enhancer/service/ItemEnhancerService.java +++ b/dspace-api/src/main/java/org/dspace/content/enhancer/service/ItemEnhancerService.java @@ -7,7 +7,11 @@ */ package org.dspace.content.enhancer.service; +import java.sql.SQLException; +import java.util.UUID; + import org.dspace.content.Item; +import org.dspace.content.dao.ItemForMetadataEnhancementUpdateDAO; import org.dspace.core.Context; /** @@ -20,7 +24,15 @@ public interface ItemEnhancerService { /** * Enhances the given item with all the item enhancers defined adding virtual - * metadata fields on it. + * metadata fields on it. ItemEnhancer will use the information stored in the + * source metadata to decide if virtual metadata must be calculated. This could + * lead to stale information if the given item is linked to the same related items + * than before but in the mean time the related items have been changed in a way + * that could affect the generated virtual metadata (for example a publication + * listing 3 authors assuming that we are flatting on the publication the information + * about the author current affiliation would not update the virtual affiliation + * if this method is invoked on the item without touching the author list - in this + * scenario you need to use the {@link #forceEnhancement(Context, Item)} method * * @param context the DSpace Context * @param item the item to enhance @@ -29,4 +41,21 @@ public interface ItemEnhancerService { */ void enhance(Context context, Item item, boolean deepMode); + /** + * Find items that could be affected by a change of the item with given uuid + * and save them to db for future processing + * + * @param context the DSpace Context + * @param uuid UUID of the changed item + */ + void saveAffectedItemsForUpdate(Context context, UUID uuid) throws SQLException; + + /** + * Extract the first uuid in the itemupdate_metadata_enhancement table, see + * {@link ItemForMetadataEnhancementUpdateDAO#pollItemToUpdate(Context)} + * + * @param context the DSpace Context + * @return UUID of the older item queued for update + */ + UUID pollItemToUpdate(Context context); } diff --git a/dspace-api/src/main/java/org/dspace/content/enhancer/service/impl/ItemEnhancerServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/enhancer/service/impl/ItemEnhancerServiceImpl.java index e751a431ac37..ee2444e05ee1 100644 --- a/dspace-api/src/main/java/org/dspace/content/enhancer/service/impl/ItemEnhancerServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/enhancer/service/impl/ItemEnhancerServiceImpl.java @@ -9,9 +9,12 @@ import java.sql.SQLException; import java.util.List; +import java.util.UUID; +import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; +import org.dspace.content.dao.ItemForMetadataEnhancementUpdateDAO; import org.dspace.content.enhancer.ItemEnhancer; import org.dspace.content.enhancer.service.ItemEnhancerService; import org.dspace.content.service.ItemService; @@ -26,27 +29,54 @@ */ public class ItemEnhancerServiceImpl implements ItemEnhancerService { + /** + * log4j category + */ + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemEnhancerServiceImpl.class); + @Autowired private List itemEnhancers; @Autowired private ItemService itemService; + @Autowired + private ItemForMetadataEnhancementUpdateDAO itemForMetadataEnhancementUpdateDAO; + @Override public void enhance(Context context, Item item, boolean deepMode) { boolean isUpdateNeeded = false; - + if (deepMode) { + final UUID id = item.getID(); + log.debug("deepMode enabled, removing item with uuid {} from the queue", id); + itemForMetadataEnhancementUpdateDAO.removeItemForUpdate(context, id); + } for (ItemEnhancer itemEnhancer : itemEnhancers) { if (itemEnhancer.canEnhance(context, item)) { isUpdateNeeded = itemEnhancer.enhance(context, item, deepMode) || isUpdateNeeded; } } - if (isUpdateNeeded) { updateItem(context, item); + try { + saveAffectedItemsForUpdate(context, item.getID()); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } } } + @Override + public void saveAffectedItemsForUpdate(Context context, UUID uuid) throws SQLException { + int queued = itemForMetadataEnhancementUpdateDAO.saveAffectedItemsForUpdate(context, uuid); + log.debug("queued {} items for metadata enhancement check", queued); + } + + @Override + public UUID pollItemToUpdate(Context context) { + return itemForMetadataEnhancementUpdateDAO.pollItemToUpdate(context); + } + private void updateItem(Context context, Item item) { try { itemService.update(context, item); diff --git a/dspace-api/src/main/java/org/dspace/content/integration/crosswalks/virtualfields/VirtualFieldToEnhancedMetadata.java b/dspace-api/src/main/java/org/dspace/content/integration/crosswalks/virtualfields/VirtualFieldToEnhancedMetadata.java new file mode 100644 index 000000000000..2ea11f6c4ea8 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/integration/crosswalks/virtualfields/VirtualFieldToEnhancedMetadata.java @@ -0,0 +1,62 @@ +/** + * 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.integration.crosswalks.virtualfields; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.enhancer.impl.RelatedEntityItemEnhancerUtils; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.core.CrisConstants; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of {@link VirtualField} that returns the values from the a + * cris.virtual. metadata using the provided in the form of + * -- as source metadata. + * Source metadata that are not found in the cris.virtualsource. leads to a PLACEHOLDER + * + * @author Andrea Bollini at 4science.comm + * + */ +public class VirtualFieldToEnhancedMetadata implements VirtualField { + + @Autowired + private ItemService itemService; + + @Autowired + private RelatedEntityItemEnhancerUtils relatedEntityItemEnhancerUtils; + + @Override + public String[] getMetadata(Context context, Item item, String fieldName) { + ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + String[] fieldBits = fieldName.split("\\."); + if (fieldBits.length != 3) { + throw new IllegalArgumentException( + "VirtualFieldToEnhancedMetadata must be used specifying the EnhancedMetadata qualifier as " + + "element and the source metadata as qualifier, i.e. virtual.department.dc-contributor-author"); + } + String virtualQualifier = fieldBits[1]; + String metadata = fieldBits[2].replaceAll("-", "."); + Map> map = relatedEntityItemEnhancerUtils.getCurrentVirtualsMap(item, + virtualQualifier); + List values = itemService.getMetadataByMetadataString(item, metadata).stream() + .map(mv -> mv.getAuthority() != null && map.containsKey(mv.getAuthority()) + ? map.get(mv.getAuthority()).get(0).getValue() + : CrisConstants.PLACEHOLDER_PARENT_METADATA_VALUE) + .collect(Collectors.toList()); + String[] resultValues = values.toArray(new String[0]); + return resultValues; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index 4d3079240c3d..ad82ee83a841 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -23,6 +23,7 @@ import org.apache.logging.log4j.Logger; import org.dspace.authorize.ResourcePolicy; import org.dspace.content.DSpaceObject; +import org.dspace.core.exception.SQLRuntimeException; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; @@ -33,6 +34,7 @@ import org.dspace.storage.rdbms.DatabaseConfigVO; import org.dspace.storage.rdbms.DatabaseUtils; import org.dspace.utils.DSpace; +import org.hibernate.Session; import org.springframework.util.CollectionUtils; /** @@ -451,6 +453,14 @@ public void commit() throws SQLException { } } + public void clear() { + try { + ((Session) dbConnection.getSession()).clear(); + reloadContextBoundEntities(); + } catch (SQLException e) { + throw new SQLRuntimeException(e); + } + } /** * Dispatch any events (cached in current Context) to configured EventListeners (consumers) diff --git a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java index 858149a64f31..c76e818ebffd 100644 --- a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java +++ b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java @@ -20,6 +20,7 @@ import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.handle.Handle; +import org.dspace.identifier.DOI; import org.dspace.storage.rdbms.DatabaseConfigVO; import org.hibernate.FlushMode; import org.hibernate.Hibernate; @@ -280,6 +281,11 @@ public void uncacheEntity(E entity) throws SQLExcep } } + if (entity instanceof DOI) { + DOI doi = (DOI) entity; + uncacheEntity(doi.getDSpaceObject()); + } + // ITEM if (entity instanceof Item) { Item item = (Item) entity; diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceFileInfoPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceFileInfoPlugin.java index a2c3056ae38d..590527025aaf 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceFileInfoPlugin.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceFileInfoPlugin.java @@ -8,15 +8,10 @@ package org.dspace.discovery; import java.sql.SQLException; -import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; import org.apache.solr.common.SolrInputDocument; @@ -24,12 +19,10 @@ import org.dspace.content.BitstreamFormat; import org.dspace.content.Bundle; import org.dspace.content.MetadataField; -import org.dspace.content.MetadataFieldName; -import org.dspace.content.MetadataValue; import org.dspace.core.Context; +import org.dspace.discovery.index.adder.IndexAdder; +import org.dspace.discovery.index.mapper.SolrFieldMetadataMapper; import org.dspace.discovery.indexobject.IndexableItem; -import org.joda.time.format.DateTimeFormat; -import org.joda.time.format.DateTimeFormatter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,132 +49,23 @@ */ public class SolrServiceFileInfoPlugin implements SolrServiceIndexPlugin { - /** - * Class used to map a target metadata into a solr index using {@code SolrInputDocument} - * - * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com) - * - * @param - */ - private static class SolrFieldMetadataMapper { - private final String solrField; - private final BiFunction> fieldAdder; - - public SolrFieldMetadataMapper( - String metadata, - BiFunction> fieldAdder - ) { - super(); - this.solrField = metadata; - this.fieldAdder = fieldAdder; - } - - public void map(SolrInputDocument document, T value) { - this.fieldAdder.apply(document, this.solrField).accept(value); - } - - } - private static final Logger logger = LoggerFactory.getLogger(SolrServiceFileInfoPlugin.class); - private static final DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyy-MM-dd"); private static final String BUNDLE_NAME = "ORIGINAL"; private static final String SOLR_FIELD_NAME_FOR_FILENAMES = "original_bundle_filenames"; private static final String SOLR_FIELD_NAME_FOR_DESCRIPTIONS = "original_bundle_descriptions"; - private static final String SOLR_FIELD_NAME_FOR_OAIRE_LICENSE_CONDITION = "original_bundle_oaire_licenseCondition"; - private static final String SOLR_FIELD_NAME_FOR_DATACITE_RIGHTS = "original_bundle_datacite_rights"; - private static final String SOLR_FIELD_NAME_FOR_DATACITE_AVAILABLE = "original_bundle_datacite_available"; private static final String SOLR_FIELD_NAME_FOR_MIMETYPE = "original_bundle_mime_type"; private static final String SOLR_FIELD_NAME_FOR_CHECKSUM = "original_bundle_checksum"; private static final String SOLR_FIELD_NAME_FOR_SIZEBYTES = "original_bundle_sizebytes"; private static final String SOLR_FIELD_NAME_FOR_SHORT_DESCRIPTION = "original_bundle_short_description"; - private static final String SOLR_POSTFIX_FILTER = "_filter"; - private static final String SOLR_POSTFIX_KEYWORD = "_keyword"; - private static final String BITSTREAM_METADATA_SOLR_PREFIX_KEYWORD = "bitstreams."; - // used for facets and filters of type Date to correctly search them and visualize in facets. - private static final String SOLR_POSTFIX_YEAR = ".year"; - private static final MetadataFieldName METADATA_DATACITE_RIGHTS = new MetadataFieldName("datacite", "rights"); - private static final MetadataFieldName METADATA_DATACITE_AVAILABLE = new MetadataFieldName("datacite", "available"); - private static final MetadataFieldName METADATA_LICENSE_CONDITION = - new MetadataFieldName("oaire", "licenseCondition"); - private static final BiFunction> defaultSolrIndexAdder = - (document, fieldName) -> value -> { - Collection fieldValues = document.getFieldValues(fieldName); - if (fieldValues == null || !fieldValues.contains(value)) { - addField(document, fieldName, value); - addField(document, fieldName.concat(SOLR_POSTFIX_KEYWORD), value); - addField(document, fieldName.concat(SOLR_POSTFIX_FILTER), value); - } - }; + private Map mappableMetadata; - private static final BiFunction> simpleSolrIndexAdder = - (document, fieldName) -> value -> { - Collection fieldValues = document.getFieldValues(fieldName); - if (fieldValues == null || !fieldValues.contains(value)) { - addField(document, fieldName, value); - } - }; + private IndexAdder simpleIndexAdder; - private static final BiFunction> bitstreamMetadataSolrIndexAdder = - (document, fieldName) -> value -> { - String baseIndex = BITSTREAM_METADATA_SOLR_PREFIX_KEYWORD.concat(fieldName); - Collection fieldValues = document.getFieldValues(baseIndex); - if (fieldValues == null || !fieldValues.contains(value)) { - addField(document, baseIndex, value); - addField(document, baseIndex.concat(SOLR_POSTFIX_KEYWORD), value); - addField(document, baseIndex.concat(SOLR_POSTFIX_FILTER), value); - } - }; + private IndexAdder defaultIndexAdder; - private static final BiFunction> yearSolrIndexAdder = - (document, fieldName) -> value -> { - Collection fieldValues = document.getFieldValues(fieldName); - if (fieldValues == null || !fieldValues.contains(value)) { - addField(document, fieldName, value); - addField(document, fieldName.concat(SOLR_POSTFIX_KEYWORD), value); - addField(document, fieldName.concat(SOLR_POSTFIX_FILTER), value); - addField(document, fieldName.concat(SOLR_POSTFIX_YEAR), dtf.parseLocalDate(value).getYear()); - } - }; - - private static final SolrFieldMetadataMapper getFieldMapper( - String solrField, - BiFunction> adder - ) { - return new SolrFieldMetadataMapper(solrField, adder); - } - - private static final SolrFieldMetadataMapper OAIRE_LICENSE_MAPPER = - new SolrFieldMetadataMapper( - SOLR_FIELD_NAME_FOR_OAIRE_LICENSE_CONDITION, - defaultSolrIndexAdder - ); - - private static final SolrFieldMetadataMapper DATACITE_RIGHTS_MAPPER = - new SolrFieldMetadataMapper( - SOLR_FIELD_NAME_FOR_DATACITE_RIGHTS, - defaultSolrIndexAdder - ); - - private static final SolrFieldMetadataMapper DATACITE_AVAILABLE_MAPPER = - new SolrFieldMetadataMapper( - SOLR_FIELD_NAME_FOR_DATACITE_AVAILABLE, - yearSolrIndexAdder - ); - - private static final Map> mappableMetadatas = - Stream.of( - Map.entry(METADATA_LICENSE_CONDITION.toString(), OAIRE_LICENSE_MAPPER), - Map.entry(METADATA_DATACITE_RIGHTS.toString(), DATACITE_RIGHTS_MAPPER), - Map.entry(METADATA_DATACITE_AVAILABLE.toString(), DATACITE_AVAILABLE_MAPPER) - ) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - - private static void addField(SolrInputDocument document, String name, Object value) { - document.addField(name, value); - } + private IndexAdder bitstreamMetadataIndexAdder; @Override public void additionalIndex(Context context, IndexableObject indexableObject, SolrInputDocument document) { @@ -221,7 +105,7 @@ private void generateBitstreamIndex(Context context, SolrInputDocument document, private void indexBitstreamFields(Context context, SolrInputDocument document, Bitstream bitstream) { addAndHandleException( - simpleSolrIndexAdder, document, bitstream, SOLR_FIELD_NAME_FOR_FILENAMES, bitstream.getName() + simpleIndexAdder, document, bitstream, SOLR_FIELD_NAME_FOR_FILENAMES, bitstream.getName() ); Optional.ofNullable(bitstream.getDescription()) @@ -229,7 +113,7 @@ private void indexBitstreamFields(Context context, SolrInputDocument document, B .ifPresent( (description) -> addAndHandleException( - simpleSolrIndexAdder, document, bitstream, SOLR_FIELD_NAME_FOR_DESCRIPTIONS, description + simpleIndexAdder, document, bitstream, SOLR_FIELD_NAME_FOR_DESCRIPTIONS, description ) ); @@ -243,7 +127,7 @@ private void indexBitstreamFields(Context context, SolrInputDocument document, B .filter(StringUtils::isNotBlank) .ifPresent(format -> addAndHandleException( - defaultSolrIndexAdder, document, bitstream, SOLR_FIELD_NAME_FOR_MIMETYPE, format + defaultIndexAdder, document, bitstream, SOLR_FIELD_NAME_FOR_MIMETYPE, format ) ); @@ -251,7 +135,7 @@ private void indexBitstreamFields(Context context, SolrInputDocument document, B .map(BitstreamFormat::getShortDescription) .ifPresent(format -> addAndHandleException( - simpleSolrIndexAdder, document, bitstream, SOLR_FIELD_NAME_FOR_SHORT_DESCRIPTION, format + defaultIndexAdder, document, bitstream, SOLR_FIELD_NAME_FOR_SHORT_DESCRIPTION, format ) ); } catch (SQLException e) { @@ -264,7 +148,7 @@ private void indexBitstreamFields(Context context, SolrInputDocument document, B .map(checksum -> bitstream.getChecksumAlgorithm() + ":" + bitstream.getChecksum()) .ifPresent(checksum -> addAndHandleException( - defaultSolrIndexAdder, document, bitstream, SOLR_FIELD_NAME_FOR_CHECKSUM, checksum + defaultIndexAdder, document, bitstream, SOLR_FIELD_NAME_FOR_CHECKSUM, checksum ) ); @@ -273,18 +157,18 @@ private void indexBitstreamFields(Context context, SolrInputDocument document, B .map(String::valueOf) .ifPresent(size -> addAndHandleException( - simpleSolrIndexAdder, document, bitstream, SOLR_FIELD_NAME_FOR_SIZEBYTES, size + simpleIndexAdder, document, bitstream, SOLR_FIELD_NAME_FOR_SIZEBYTES, size ) ); } protected void addAndHandleException( - BiFunction> solrIndexAdder, + IndexAdder indexAdder, SolrInputDocument document, Bitstream bitstream, String field, String value ) { try { - solrIndexAdder.apply(document, field).accept(value); + indexAdder.add(document, field, value); } catch (Exception e) { logger.warn( "Error occurred during the update of index field {} for bitstream {}", @@ -302,21 +186,25 @@ private void indexBitstreamsMetadatadas(SolrInputDocument document, Bitstream bi .forEach(metadata -> { MetadataField metadataField = metadata.getMetadataField(); String bitstreamMetadata = metadataField.toString('.'); - Optional.ofNullable(mappableMetadatas.get(bitstreamMetadata)) - .filter(Objects::nonNull) - .orElse( - getFieldMapper( - metadataField.toString(), - bitstreamMetadataSolrIndexAdder - ) - ) + Optional.ofNullable(mappableMetadata.get(bitstreamMetadata)) + .orElseGet(() -> new SolrFieldMetadataMapper(metadataField.toString(), bitstreamMetadataIndexAdder)) .map(document, metadata.getValue()); }); } - private boolean areEquals(MetadataFieldName metadataFieldName, MetadataValue metadata) { - return StringUtils.equals(metadataFieldName.schema, metadata.getSchema()) && - StringUtils.equals(metadataFieldName.element, metadata.getElement()) && - StringUtils.equals(metadataFieldName.qualifier, metadata.getQualifier()); + public void setMappableMetadata(Map mappableMetadata) { + this.mappableMetadata = mappableMetadata; + } + + public void setSimpleIndexAdder(IndexAdder simpleIndexAdder) { + this.simpleIndexAdder = simpleIndexAdder; + } + + public void setDefaultIndexAdder(IndexAdder defaultIndexAdder) { + this.defaultIndexAdder = defaultIndexAdder; + } + + public void setBitstreamMetadataIndexAdder(IndexAdder bitstreamMetadataIndexAdder) { + this.bitstreamMetadataIndexAdder = bitstreamMetadataIndexAdder; } } diff --git a/dspace-api/src/main/java/org/dspace/discovery/index/adder/BitstreamMetadataSolrIndexAdder.java b/dspace-api/src/main/java/org/dspace/discovery/index/adder/BitstreamMetadataSolrIndexAdder.java new file mode 100644 index 000000000000..3541178d1c48 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/index/adder/BitstreamMetadataSolrIndexAdder.java @@ -0,0 +1,34 @@ +/** + * 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.discovery.index.adder; + +import java.util.Collection; + +import org.apache.solr.common.SolrInputDocument; + +/** + * Implementation of {@link IndexAdder} defining default bitstream metadata index adder. + * + * @author Nikita Krisonov + * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com) + */ +public class BitstreamMetadataSolrIndexAdder implements IndexAdder { + + private static final String BITSTREAM_METADATA_SOLR_PREFIX_KEYWORD = "bitstreams."; + + @Override + public void add(SolrInputDocument document, String solrFieldName, String value) { + String baseIndex = BITSTREAM_METADATA_SOLR_PREFIX_KEYWORD.concat(solrFieldName); + Collection fieldValues = document.getFieldValues(baseIndex); + if (fieldValues == null || !fieldValues.contains(value)) { + document.addField(solrFieldName, value); + document.addField(solrFieldName.concat(SOLR_POSTFIX_KEYWORD), value); + document.addField(solrFieldName.concat(SOLR_POSTFIX_FILTER), value); + } + } +} diff --git a/dspace-api/src/main/java/org/dspace/discovery/index/adder/DefaultSolrIndexAdder.java b/dspace-api/src/main/java/org/dspace/discovery/index/adder/DefaultSolrIndexAdder.java new file mode 100644 index 000000000000..0b540d6a7a66 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/index/adder/DefaultSolrIndexAdder.java @@ -0,0 +1,28 @@ +/** + * 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.discovery.index.adder; + +import java.util.Collection; + +import org.apache.solr.common.SolrInputDocument; + +/** + * Implementation of {@link IndexAdder} defining default index adder. + */ +public class DefaultSolrIndexAdder implements IndexAdder { + + @Override + public void add(SolrInputDocument document, String solrFieldName, String value) { + Collection fieldValues = document.getFieldValues(solrFieldName); + if (fieldValues == null || !fieldValues.contains(value)) { + document.addField(solrFieldName, value); + document.addField(solrFieldName.concat(SOLR_POSTFIX_KEYWORD), value); + document.addField(solrFieldName.concat(SOLR_POSTFIX_FILTER), value); + } + } +} diff --git a/dspace-api/src/main/java/org/dspace/discovery/index/adder/IndexAdder.java b/dspace-api/src/main/java/org/dspace/discovery/index/adder/IndexAdder.java new file mode 100644 index 000000000000..aa3307061bcd --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/index/adder/IndexAdder.java @@ -0,0 +1,32 @@ +/** + * 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.discovery.index.adder; + +import org.apache.solr.common.SolrInputDocument; + +/** + * Interface for defining strategy of adding an index. + * + * @author Nikita Krisonov + * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com) + */ +public interface IndexAdder { + + String SOLR_POSTFIX_FILTER = "_filter"; + + String SOLR_POSTFIX_KEYWORD = "_keyword"; + + /** + * Adds new index to the document based on passed solr field name and value. + * + * @param document document which will get a new index + * @param solrField solr field name + * @param value solr field value. + */ + void add(SolrInputDocument document, String solrField, String value); +} diff --git a/dspace-api/src/main/java/org/dspace/discovery/index/adder/SimpleSolrIndexAdder.java b/dspace-api/src/main/java/org/dspace/discovery/index/adder/SimpleSolrIndexAdder.java new file mode 100644 index 000000000000..551689abaceb --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/index/adder/SimpleSolrIndexAdder.java @@ -0,0 +1,29 @@ +/** + * 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.discovery.index.adder; + +import java.util.Collection; + +import org.apache.solr.common.SolrInputDocument; + +/** + * Implementation of {@link IndexAdder} defining simple index adder without any postfixes. + * + * @author Nikita Krisonov + * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com) + */ +public class SimpleSolrIndexAdder implements IndexAdder { + + @Override + public void add(SolrInputDocument document, String solrFieldName, String value) { + Collection fieldValues = document.getFieldValues(solrFieldName); + if (fieldValues == null || !fieldValues.contains(value)) { + document.addField(solrFieldName, value); + } + } +} diff --git a/dspace-api/src/main/java/org/dspace/discovery/index/adder/YearSolrIndexAdder.java b/dspace-api/src/main/java/org/dspace/discovery/index/adder/YearSolrIndexAdder.java new file mode 100644 index 000000000000..5ff638771382 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/index/adder/YearSolrIndexAdder.java @@ -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.discovery.index.adder; + +import java.util.Collection; + +import org.apache.solr.common.SolrInputDocument; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + +/** + * Implementation of {@link IndexAdder} defining index adder for handling year of a date. + * + * @author Nikita Krisonov + * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com) + */ +public class YearSolrIndexAdder implements IndexAdder { + + private static final DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyy-MM-dd"); + + private static final String SOLR_POSTFIX_YEAR = ".year"; + + @Override + public void add(SolrInputDocument document, String solrFieldName, String value) { + Collection fieldValues = document.getFieldValues(solrFieldName); + if (fieldValues == null || !fieldValues.contains(value)) { + document.addField(solrFieldName, value); + document.addField(solrFieldName.concat(SOLR_POSTFIX_KEYWORD), value); + document.addField(solrFieldName.concat(SOLR_POSTFIX_FILTER), value); + document.addField(solrFieldName.concat(SOLR_POSTFIX_YEAR), dtf.parseLocalDate(value).getYear()); + } + } +} diff --git a/dspace-api/src/main/java/org/dspace/discovery/index/mapper/SolrFieldMetadataMapper.java b/dspace-api/src/main/java/org/dspace/discovery/index/mapper/SolrFieldMetadataMapper.java new file mode 100644 index 000000000000..932b42a51647 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/index/mapper/SolrFieldMetadataMapper.java @@ -0,0 +1,41 @@ +/** + * 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.discovery.index.mapper; + +import org.apache.solr.common.SolrInputDocument; +import org.dspace.discovery.index.adder.IndexAdder; + +/** + * Class used to map a target metadata into a solr index using {@code SolrInputDocument} + * + * @author Vincenzo Mecca (vins01-4science - vincenzo.mecca at 4science.com) + */ +public class SolrFieldMetadataMapper { + + private final String solrField; + + private final IndexAdder fieldAdder; + + public SolrFieldMetadataMapper( + String metadata, + IndexAdder indexAdder + ) { + this.solrField = metadata; + this.fieldAdder = indexAdder; + } + + /** + * Adds new index into the {@code document} based on passed {@code value}. + * + * @param document document which will get a new index + * @param value value of new index. + */ + public void map(SolrInputDocument document, String value) { + fieldAdder.add(document, solrField, value); + } +} diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java index 9897639f04a6..7ffd7ecd004d 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/LiveImportDataProvider.java @@ -92,7 +92,7 @@ public void setDisplayMetadata(String displayMetadata) { public Optional getExternalDataObject(String id) { try { ExternalDataObject externalDataObject = getExternalDataObject(querySource.getRecord(id)); - return Optional.of(externalDataObject); + return Optional.ofNullable(externalDataObject); } catch (MetadataSourceException e) { throw new RuntimeException( "The live import provider " + querySource.getImportSource() + " throws an exception", e); diff --git a/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java b/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java index 99643db33fa0..2e726234a284 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/identifier/DOIServiceImpl.java @@ -161,7 +161,13 @@ public String formatIdentifier(String identifier) throws DOIIdentifierException @Override public List getDOIsByStatus(Context context, List statuses) throws SQLException { - return doiDAO.findByStatus(context, statuses); + return doiDAO.findByStatus(context, statuses, -1, -1); + } + + @Override + public List getDOIsByStatus(Context context, List statuses, int offset, int limit) + throws SQLException { + return doiDAO.findByStatus(context, statuses, offset, limit); } @Override diff --git a/dspace-api/src/main/java/org/dspace/identifier/dao/DOIDAO.java b/dspace-api/src/main/java/org/dspace/identifier/dao/DOIDAO.java index 8085c6599b69..df75ced157ab 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/dao/DOIDAO.java +++ b/dspace-api/src/main/java/org/dspace/identifier/dao/DOIDAO.java @@ -31,7 +31,7 @@ public DOI findDOIByDSpaceObject(Context context, DSpaceObject dso, List findSimilarNotInState(Context context, String doi, List statuses, boolean dsoNotNull) throws SQLException; - public List findByStatus(Context context, List statuses) throws SQLException; + public List findByStatus(Context context, List statuses, int offset, int limit) throws SQLException; public DOI findDOIByDSpaceObject(Context context, DSpaceObject dso) throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/identifier/dao/impl/DOIDAOImpl.java b/dspace-api/src/main/java/org/dspace/identifier/dao/impl/DOIDAOImpl.java index 784fec1d8894..0811a7adfb61 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/dao/impl/DOIDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/identifier/dao/impl/DOIDAOImpl.java @@ -70,7 +70,7 @@ public DOI findDOIByDSpaceObject(Context context, DSpaceObject dso, List findByStatus(Context context, List statuses) throws SQLException { + public List findByStatus(Context context, List statuses, int offset, int limit) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, DOI.class); Root doiRoot = criteriaQuery.from(DOI.class); @@ -80,7 +80,7 @@ public List findByStatus(Context context, List statuses) throws SQ orPredicates.add(criteriaBuilder.equal(doiRoot.get(DOI_.status), status)); } criteriaQuery.where(criteriaBuilder.or(orPredicates.toArray(new Predicate[] {}))); - return list(context, criteriaQuery, false, DOI.class, -1, -1); + return list(context, criteriaQuery, false, DOI.class, limit, offset); } @Override 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 ad885314bb2b..a90ccc0980b2 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 @@ -132,6 +132,8 @@ public static void runCLI(Context context, DOIOrganiser organiser, String[] args "Perform online deletion for all identifiers queued for deletion."); options.addOption("q", "quiet", false, "Turn the command line output off."); + options.addOption("o", "offset", true, "The records offset"); + options.addOption("li", "limit", true, "The records limit"); Option filterDoi = Option.builder().optionalArg(true).longOpt("filter").hasArg().argName("filterName") .desc("Use the specified filter name instead of the provider's filter. Defaults to a special " + @@ -212,6 +214,17 @@ public static void runCLI(Context context, DOIOrganiser organiser, String[] args organiser.list("deletion", null, null, DOIIdentifierProvider.TO_BE_DELETED); } + int limit = -1; + int offset = -1; + + if (line.hasOption("li")) { + limit = Integer.valueOf(line.getOptionValue("li")); + } + + if (line.hasOption("o")) { + offset = Integer.valueOf(line.getOptionValue("o")); + } + DOIService doiService = IdentifierServiceFactory.getInstance().getDOIService(); // Do we get a filter? if (line.hasOption("filter")) { @@ -224,7 +237,7 @@ public static void runCLI(Context context, DOIOrganiser organiser, String[] args if (line.hasOption('s')) { try { List dois = doiService - .getDOIsByStatus(context, Arrays.asList(DOIIdentifierProvider.TO_BE_RESERVED)); + .getDOIsByStatus(context, Arrays.asList(DOIIdentifierProvider.TO_BE_RESERVED), offset, limit); if (dois.isEmpty()) { System.err.println("There are no objects in the database " + "that could be reserved."); @@ -243,7 +256,7 @@ public static void runCLI(Context context, DOIOrganiser organiser, String[] args if (line.hasOption('r')) { try { List dois = doiService - .getDOIsByStatus(context, Arrays.asList(DOIIdentifierProvider.TO_BE_REGISTERED)); + .getDOIsByStatus(context, Arrays.asList(DOIIdentifierProvider.TO_BE_REGISTERED), offset, limit); if (dois.isEmpty()) { System.err.println("There are no objects in the database " + "that could be registered."); @@ -265,7 +278,7 @@ public static void runCLI(Context context, DOIOrganiser organiser, String[] args List dois = doiService.getDOIsByStatus(context, Arrays.asList( DOIIdentifierProvider.UPDATE_BEFORE_REGISTRATION, DOIIdentifierProvider.UPDATE_RESERVED, - DOIIdentifierProvider.UPDATE_REGISTERED)); + DOIIdentifierProvider.UPDATE_REGISTERED), offset, limit); if (dois.isEmpty()) { System.err.println("There are no objects in the database " + "whose metadata needs an update."); @@ -284,7 +297,7 @@ public static void runCLI(Context context, DOIOrganiser organiser, String[] args if (line.hasOption('d')) { try { List dois = doiService - .getDOIsByStatus(context, Arrays.asList(DOIIdentifierProvider.TO_BE_DELETED)); + .getDOIsByStatus(context, Arrays.asList(DOIIdentifierProvider.TO_BE_DELETED), offset, limit); if (dois.isEmpty()) { System.err.println("There are no objects in the database " + "that could be deleted."); diff --git a/dspace-api/src/main/java/org/dspace/identifier/service/DOIService.java b/dspace-api/src/main/java/org/dspace/identifier/service/DOIService.java index 5bd68a90615f..e8236a013f7e 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/service/DOIService.java +++ b/dspace-api/src/main/java/org/dspace/identifier/service/DOIService.java @@ -124,6 +124,18 @@ public String formatIdentifier(String identifier) */ public List getDOIsByStatus(Context context, List statuses) throws SQLException; + /** + * Find all DOIs that have one of a given set of statuses. + * @param context current DSpace session. + * @param statuses desired statuses. + * @param offset offset value + * @param limit limited number of items + * @return all DOIs having any of the given statuses. + * @throws SQLException passed through. + */ + public List getDOIsByStatus(Context context, List statuses, int offset, int limit) + throws SQLException; + /** * Find all DOIs that are similar to the specified pattern and not in the * specified states. diff --git a/dspace-api/src/main/java/org/dspace/iiif/consumer/IIIFCacheEventConsumer.java b/dspace-api/src/main/java/org/dspace/iiif/consumer/IIIFCacheEventConsumer.java index 1d6a6783018c..c5614bd1b98c 100644 --- a/dspace-api/src/main/java/org/dspace/iiif/consumer/IIIFCacheEventConsumer.java +++ b/dspace-api/src/main/java/org/dspace/iiif/consumer/IIIFCacheEventConsumer.java @@ -125,9 +125,13 @@ public void consume(Context ctx, Event event) throws Exception { private void addToCacheEviction(DSpaceObject subject, DSpaceObject subject2, int type) { if (type == Constants.BITSTREAM) { - toEvictFromCanvasCache.add(subject2); + if (subject2 != null) { + toEvictFromCanvasCache.add(subject2); + } + } + if (subject != null) { + toEvictFromManifestCache.add(subject); } - toEvictFromManifestCache.add(subject); } @Override diff --git a/dspace-api/src/main/java/org/dspace/layout/CrisLayoutBoxTypes.java b/dspace-api/src/main/java/org/dspace/layout/CrisLayoutBoxTypes.java index 198d00636431..83ba6e7996ff 100644 --- a/dspace-api/src/main/java/org/dspace/layout/CrisLayoutBoxTypes.java +++ b/dspace-api/src/main/java/org/dspace/layout/CrisLayoutBoxTypes.java @@ -12,5 +12,6 @@ public enum CrisLayoutBoxTypes { METADATA, RELATION, METRICS, - COLLECTIONS + COLLECTIONS, + VERSIONING } diff --git a/dspace-api/src/main/java/org/dspace/layout/service/impl/CrisLayoutBoxServiceImpl.java b/dspace-api/src/main/java/org/dspace/layout/service/impl/CrisLayoutBoxServiceImpl.java index c15a2f792bc8..6ec223b7bc28 100644 --- a/dspace-api/src/main/java/org/dspace/layout/service/impl/CrisLayoutBoxServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/layout/service/impl/CrisLayoutBoxServiceImpl.java @@ -41,6 +41,7 @@ import org.dspace.layout.service.CrisLayoutBoxAccessService; import org.dspace.layout.service.CrisLayoutBoxService; import org.dspace.metrics.CrisItemMetricsService; +import org.dspace.versioning.service.VersionHistoryService; import org.springframework.beans.factory.annotation.Autowired; /** @@ -71,6 +72,9 @@ public class CrisLayoutBoxServiceImpl implements CrisLayoutBoxService { @Autowired private BitstreamService bitstreamService; + @Autowired + private VersionHistoryService versionHistoryService; + public CrisLayoutBoxServiceImpl() { } @@ -164,6 +168,8 @@ public boolean hasContent(Context context, CrisLayoutBox box, Item item) { return isOwningCollectionPresent(item); case "IIIFVIEWER": return isIiifEnabled(item); + case "VERSIONING": + return hasVersioningBox(context, item); case "METADATA": default: return hasMetadataBoxContent(context, box, item); @@ -171,6 +177,13 @@ public boolean hasContent(Context context, CrisLayoutBox box, Item item) { } + private boolean hasVersioningBox(Context context, Item item) { + try { + return versionHistoryService.hasVersionHistory(context, item); + } catch (SQLException e) { + return false; + } + } @Override public boolean hasAccess(Context context, CrisLayoutBox box, Item item) { return crisLayoutBoxAccessService.hasAccess(context, context.getCurrentUser(), box, item); diff --git a/dspace-api/src/main/java/org/dspace/orcid/model/OrcidEntityType.java b/dspace-api/src/main/java/org/dspace/orcid/model/OrcidEntityType.java index c777573c1b30..8b0e0fe38c49 100644 --- a/dspace-api/src/main/java/org/dspace/orcid/model/OrcidEntityType.java +++ b/dspace-api/src/main/java/org/dspace/orcid/model/OrcidEntityType.java @@ -22,6 +22,16 @@ public enum OrcidEntityType { */ PUBLICATION("Publication", "/work"), + /** + * The ORCID product/work activity. + */ + PRODUCT("Product", "/work"), + + /** + * The ORCID patent/work activity. + */ + PATENT("Patent", "/work"), + /** * The ORCID funding activity. */ diff --git a/dspace-api/src/main/java/org/dspace/orcid/model/OrcidPatentWorkFieldMapping.java b/dspace-api/src/main/java/org/dspace/orcid/model/OrcidPatentWorkFieldMapping.java new file mode 100644 index 000000000000..4de7bb287570 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/orcid/model/OrcidPatentWorkFieldMapping.java @@ -0,0 +1,287 @@ +/** + * 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.orcid.model; + +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; +import static org.dspace.orcid.model.factory.OrcidFactoryUtils.parseConfigurations; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.content.integration.crosswalks.CSLItemDataCrosswalk; +import org.dspace.util.SimpleMapConverter; +import org.orcid.jaxb.model.common.CitationType; +import org.orcid.jaxb.model.common.ContributorRole; +import org.orcid.jaxb.model.v3.release.record.Work; + +/** + * Class that contains all the mapping between {@link Work} and DSpace metadata + * fields. Adapted from {@link OrcidWorkFieldMapping} for Patent entity + */ +public class OrcidPatentWorkFieldMapping { + + /** + * The metadata fields related to the work contributors. + */ + private Map contributorFields = new HashMap<>(); + + /** + * The metadata fields related to the work external identifiers. + */ + private Map externalIdentifierFields = new HashMap<>(); + + /** + * The metadata field related to the work publication date. + */ + private String publicationDateField; + + /** + * The metadata field related to the work title. + */ + private String titleField; + + /** + * The metadata field related to the work type. + */ + private String typeField; + + /** + * The metadata field related to the work journal title. + */ + private String journalTitleField; + + /** + * The metadata field related to the work description. + */ + private String shortDescriptionField; + + /** + * The metadata field related to the work language. + */ + private String languageField; + + /** + * The metadata field related to the work sub title. + */ + private String subTitleField; + + private CitationType citationType; + + /** + * The work type converter. + */ + private SimpleMapConverter typeConverter; + + /** + * The work language converter. + */ + private SimpleMapConverter languageConverter; + + private Map citationCrosswalks; + + private String fundingField; + + private String fundingExternalIdType; + + private String fundingExternalId; + + private String fundingEntityExternalId; + + private String fundingUrlField; + + public String convertType(String type) { + return typeConverter != null ? typeConverter.getValue(type) : type; + } + + public String convertLanguage(String language) { + return languageConverter != null ? languageConverter.getValue(language) : language; + } + + public String getTitleField() { + return titleField; + } + + public void setTitleField(String titleField) { + this.titleField = titleField; + } + + public String getTypeField() { + return typeField; + } + + public void setTypeField(String typeField) { + this.typeField = typeField; + } + + public void setTypeConverter(SimpleMapConverter typeConverter) { + this.typeConverter = typeConverter; + } + + public Map getContributorFields() { + return contributorFields; + } + + public void setContributorFields(String contributorFields) { + this.contributorFields = parseContributors(contributorFields); + } + + public Map getExternalIdentifierFields() { + return externalIdentifierFields; + } + + public void setExternalIdentifierFields(String externalIdentifierFields) { + this.externalIdentifierFields = parseConfigurations(externalIdentifierFields); + } + + public String getPublicationDateField() { + return publicationDateField; + } + + public void setPublicationDateField(String publicationDateField) { + this.publicationDateField = publicationDateField; + } + + public String getJournalTitleField() { + return journalTitleField; + } + + public void setJournalTitleField(String journalTitleField) { + this.journalTitleField = journalTitleField; + } + + public String getShortDescriptionField() { + return shortDescriptionField; + } + + public void setShortDescriptionField(String shortDescriptionField) { + this.shortDescriptionField = shortDescriptionField; + } + + public String getLanguageField() { + return languageField; + } + + public void setLanguageField(String languageField) { + this.languageField = languageField; + } + + public void setLanguageConverter(SimpleMapConverter languageConverter) { + this.languageConverter = languageConverter; + } + + public String getSubTitleField() { + return subTitleField; + } + + public void setSubTitleField(String subTitleField) { + this.subTitleField = subTitleField; + } + + public Map getCitationCrosswalks() { + return citationCrosswalks; + } + + public void setCitationCrosswalks(Map citationCrosswalks) { + this.citationCrosswalks = citationCrosswalks; + } + + public String getFundingField() { + return fundingField; + } + + public void setFundingField(String fundingField) { + this.fundingField = fundingField; + } + + public String getFundingExternalIdType() { + return fundingExternalIdType; + } + + public void setFundingExternalIdType(String fundingExternalIdType) { + this.fundingExternalIdType = fundingExternalIdType; + } + + public String getFundingExternalId() { + return fundingExternalId; + } + + public void setFundingExternalId(String fundingExternalId) { + this.fundingExternalId = fundingExternalId; + } + + public String getFundingEntityExternalId() { + return fundingEntityExternalId; + } + + public void setFundingEntityExternalId(String fundingEntityExternalId) { + this.fundingEntityExternalId = fundingEntityExternalId; + } + + public String getFundingUrlField() { + return fundingUrlField; + } + + public void setFundingUrlField(String fundingUrlField) { + this.fundingUrlField = fundingUrlField; + } + + public CitationType getCitationType() { + return citationType; + } + + public void setCitationType(String citationType) { + this.citationType = parseCitationType(citationType); + } + + private CitationType parseCitationType(String citationType) { + + if (StringUtils.isBlank(citationType)) { + return null; + } + + try { + return CitationType.fromValue(citationType); + } catch (IllegalArgumentException ex) { + throw new IllegalArgumentException("The citation type " + citationType + " is invalid, " + + "allowed values are " + getAllowedCitationTypes(), ex); + } + } + + private Map parseContributors(String contributors) { + Map contributorsMap = parseConfigurations(contributors); + return contributorsMap.keySet().stream() + .collect(toMap(identity(), field -> parseContributorRole(contributorsMap.get(field)))); + } + + private ContributorRole parseContributorRole(String contributorRole) { + try { + return ContributorRole.fromValue(contributorRole); + } catch (IllegalArgumentException ex) { + throw new IllegalArgumentException("The contributor role " + contributorRole + + " is invalid, allowed values are " + getAllowedContributorRoles(), ex); + } + } + + private List getAllowedContributorRoles() { + return Arrays.asList(ContributorRole.values()).stream() + .map(ContributorRole::value) + .collect(Collectors.toList()); + } + + private List getAllowedCitationTypes() { + return Arrays.asList(CitationType.values()).stream() + .map(CitationType::value) + .collect(Collectors.toList()); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/orcid/model/OrcidProductWorkFieldMapping.java b/dspace-api/src/main/java/org/dspace/orcid/model/OrcidProductWorkFieldMapping.java new file mode 100644 index 000000000000..c3b77a59733b --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/orcid/model/OrcidProductWorkFieldMapping.java @@ -0,0 +1,287 @@ +/** + * 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.orcid.model; + +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; +import static org.dspace.orcid.model.factory.OrcidFactoryUtils.parseConfigurations; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.content.integration.crosswalks.CSLItemDataCrosswalk; +import org.dspace.util.SimpleMapConverter; +import org.orcid.jaxb.model.common.CitationType; +import org.orcid.jaxb.model.common.ContributorRole; +import org.orcid.jaxb.model.v3.release.record.Work; + +/** + * Class that contains all the mapping between {@link Work} and DSpace metadata + * fields. Adapted from {@link org.dspace.orcid.model.OrcidWorkFieldMapping} for Product entity + */ +public class OrcidProductWorkFieldMapping { + + /** + * The metadata fields related to the work contributors. + */ + private Map contributorFields = new HashMap<>(); + + /** + * The metadata fields related to the work external identifiers. + */ + private Map externalIdentifierFields = new HashMap<>(); + + /** + * The metadata field related to the work publication date. + */ + private String publicationDateField; + + /** + * The metadata field related to the work title. + */ + private String titleField; + + /** + * The metadata field related to the work type. + */ + private String typeField; + + /** + * The metadata field related to the work journal title. + */ + private String journalTitleField; + + /** + * The metadata field related to the work description. + */ + private String shortDescriptionField; + + /** + * The metadata field related to the work language. + */ + private String languageField; + + /** + * The metadata field related to the work sub title. + */ + private String subTitleField; + + private CitationType citationType; + + /** + * The work type converter. + */ + private SimpleMapConverter typeConverter; + + /** + * The work language converter. + */ + private SimpleMapConverter languageConverter; + + private Map citationCrosswalks; + + private String fundingField; + + private String fundingExternalIdType; + + private String fundingExternalId; + + private String fundingEntityExternalId; + + private String fundingUrlField; + + public String convertType(String type) { + return typeConverter != null ? typeConverter.getValue(type) : type; + } + + public String convertLanguage(String language) { + return languageConverter != null ? languageConverter.getValue(language) : language; + } + + public String getTitleField() { + return titleField; + } + + public void setTitleField(String titleField) { + this.titleField = titleField; + } + + public String getTypeField() { + return typeField; + } + + public void setTypeField(String typeField) { + this.typeField = typeField; + } + + public void setTypeConverter(SimpleMapConverter typeConverter) { + this.typeConverter = typeConverter; + } + + public Map getContributorFields() { + return contributorFields; + } + + public void setContributorFields(String contributorFields) { + this.contributorFields = parseContributors(contributorFields); + } + + public Map getExternalIdentifierFields() { + return externalIdentifierFields; + } + + public void setExternalIdentifierFields(String externalIdentifierFields) { + this.externalIdentifierFields = parseConfigurations(externalIdentifierFields); + } + + public String getPublicationDateField() { + return publicationDateField; + } + + public void setPublicationDateField(String publicationDateField) { + this.publicationDateField = publicationDateField; + } + + public String getJournalTitleField() { + return journalTitleField; + } + + public void setJournalTitleField(String journalTitleField) { + this.journalTitleField = journalTitleField; + } + + public String getShortDescriptionField() { + return shortDescriptionField; + } + + public void setShortDescriptionField(String shortDescriptionField) { + this.shortDescriptionField = shortDescriptionField; + } + + public String getLanguageField() { + return languageField; + } + + public void setLanguageField(String languageField) { + this.languageField = languageField; + } + + public void setLanguageConverter(SimpleMapConverter languageConverter) { + this.languageConverter = languageConverter; + } + + public String getSubTitleField() { + return subTitleField; + } + + public void setSubTitleField(String subTitleField) { + this.subTitleField = subTitleField; + } + + public Map getCitationCrosswalks() { + return citationCrosswalks; + } + + public void setCitationCrosswalks(Map citationCrosswalks) { + this.citationCrosswalks = citationCrosswalks; + } + + public String getFundingField() { + return fundingField; + } + + public void setFundingField(String fundingField) { + this.fundingField = fundingField; + } + + public String getFundingExternalIdType() { + return fundingExternalIdType; + } + + public void setFundingExternalIdType(String fundingExternalIdType) { + this.fundingExternalIdType = fundingExternalIdType; + } + + public String getFundingExternalId() { + return fundingExternalId; + } + + public void setFundingExternalId(String fundingExternalId) { + this.fundingExternalId = fundingExternalId; + } + + public String getFundingEntityExternalId() { + return fundingEntityExternalId; + } + + public void setFundingEntityExternalId(String fundingEntityExternalId) { + this.fundingEntityExternalId = fundingEntityExternalId; + } + + public String getFundingUrlField() { + return fundingUrlField; + } + + public void setFundingUrlField(String fundingUrlField) { + this.fundingUrlField = fundingUrlField; + } + + public CitationType getCitationType() { + return citationType; + } + + public void setCitationType(String citationType) { + this.citationType = parseCitationType(citationType); + } + + private CitationType parseCitationType(String citationType) { + + if (StringUtils.isBlank(citationType)) { + return null; + } + + try { + return CitationType.fromValue(citationType); + } catch (IllegalArgumentException ex) { + throw new IllegalArgumentException("The citation type " + citationType + " is invalid, " + + "allowed values are " + getAllowedCitationTypes(), ex); + } + } + + private Map parseContributors(String contributors) { + Map contributorsMap = parseConfigurations(contributors); + return contributorsMap.keySet().stream() + .collect(toMap(identity(), field -> parseContributorRole(contributorsMap.get(field)))); + } + + private ContributorRole parseContributorRole(String contributorRole) { + try { + return ContributorRole.fromValue(contributorRole); + } catch (IllegalArgumentException ex) { + throw new IllegalArgumentException("The contributor role " + contributorRole + + " is invalid, allowed values are " + getAllowedContributorRoles(), ex); + } + } + + private List getAllowedContributorRoles() { + return Arrays.asList(ContributorRole.values()).stream() + .map(ContributorRole::value) + .collect(Collectors.toList()); + } + + private List getAllowedCitationTypes() { + return Arrays.asList(CitationType.values()).stream() + .map(CitationType::value) + .collect(Collectors.toList()); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidPatentWorkFactory.java b/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidPatentWorkFactory.java new file mode 100644 index 000000000000..34d78e4346e4 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidPatentWorkFactory.java @@ -0,0 +1,397 @@ +/** + * 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.orcid.model.factory.impl; + +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.dspace.core.CrisConstants.PLACEHOLDER_PARENT_METADATA_VALUE; +import static org.orcid.jaxb.model.common.Relationship.FUNDED_BY; +import static org.orcid.jaxb.model.common.Relationship.SELF; + +import java.io.ByteArrayOutputStream; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.EnumUtils; +import org.apache.commons.lang3.StringUtils; +import org.dspace.authority.service.AuthorityValueService; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.integration.crosswalks.CSLItemDataCrosswalk; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.orcid.model.OrcidEntityType; +import org.dspace.orcid.model.OrcidPatentWorkFieldMapping; +import org.dspace.orcid.model.factory.OrcidCommonObjectFactory; +import org.dspace.orcid.model.factory.OrcidEntityFactory; +import org.dspace.util.UUIDUtils; +import org.orcid.jaxb.model.common.CitationType; +import org.orcid.jaxb.model.common.ContributorRole; +import org.orcid.jaxb.model.common.LanguageCode; +import org.orcid.jaxb.model.common.Relationship; +import org.orcid.jaxb.model.common.WorkType; +import org.orcid.jaxb.model.v3.release.common.Contributor; +import org.orcid.jaxb.model.v3.release.common.PublicationDate; +import org.orcid.jaxb.model.v3.release.common.Subtitle; +import org.orcid.jaxb.model.v3.release.common.Title; +import org.orcid.jaxb.model.v3.release.common.Url; +import org.orcid.jaxb.model.v3.release.record.Activity; +import org.orcid.jaxb.model.v3.release.record.Citation; +import org.orcid.jaxb.model.v3.release.record.ExternalID; +import org.orcid.jaxb.model.v3.release.record.ExternalIDs; +import org.orcid.jaxb.model.v3.release.record.Work; +import org.orcid.jaxb.model.v3.release.record.WorkContributors; +import org.orcid.jaxb.model.v3.release.record.WorkTitle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of {@link OrcidEntityFactory} that creates instances of + * {@link Work}. Copy of {@link OrcidWorkFactory} + * Adapted for Product Entity with own mapping in {@link org.dspace.orcid.model.OrcidProductWorkFieldMapping} + */ +public class OrcidPatentWorkFactory extends OrcidWorkFactory implements OrcidEntityFactory { + + private static final Logger LOGGER = LoggerFactory.getLogger(OrcidPatentWorkFactory.class); + + @Autowired + private ItemService itemService; + + @Autowired + private OrcidCommonObjectFactory orcidCommonObjectFactory; + + private OrcidPatentWorkFieldMapping fieldMapping; + + @Override + public OrcidEntityType getEntityType() { + return OrcidEntityType.PATENT; + } + + @Override + public Activity createOrcidObject(Context context, Item item) { + Work work = new Work(); + work.setJournalTitle(getJournalTitle(context, item)); + work.setWorkContributors(getWorkContributors(context, item)); + work.setWorkTitle(getWorkTitle(context, item)); + work.setPublicationDate(getPublicationDate(context, item)); + work.setWorkExternalIdentifiers(getWorkExternalIds(context, item)); + work.setWorkType(getWorkType(context, item)); + work.setWorkCitation(getWorkCitation(context, item)); + work.setShortDescription(getShortDescription(context, item)); + work.setLanguageCode(getLanguageCode(context, item)); + work.setUrl(getUrl(context, item)); + return work; + } + + private Title getJournalTitle(Context context, Item item) { + return getMetadataValue(context, item, fieldMapping.getJournalTitleField()) + .map(metadataValue -> new Title(metadataValue.getValue())) + .orElse(null); + } + + private WorkContributors getWorkContributors(Context context, Item item) { + Map contributorFields = fieldMapping.getContributorFields(); + List contributors = getMetadataValues(context, item, contributorFields.keySet()).stream() + .map(metadataValue -> getContributor(context, metadataValue)) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + return new WorkContributors(contributors); + } + + private Optional getContributor(Context context, MetadataValue metadataValue) { + Map contributorFields = fieldMapping.getContributorFields(); + ContributorRole role = contributorFields.get(metadataValue.getMetadataField().toString('.')); + return orcidCommonObjectFactory.createContributor(context, metadataValue, role); + } + + /** + * Create an instance of WorkTitle from the given item. + */ + private WorkTitle getWorkTitle(Context context, Item item) { + Optional workTitleValue = getWorkTitleValue(context, item); + if (workTitleValue.isEmpty()) { + return null; + } + + WorkTitle workTitle = new WorkTitle(); + workTitle.setTitle(new Title(workTitleValue.get())); + getSubTitle(context, item).ifPresent(workTitle::setSubtitle); + return workTitle; + } + + /** + * Take the work title from the configured metadata field of the given item + * (orcid.mapping.work.title), if any. + */ + private Optional getWorkTitleValue(Context context, Item item) { + return getMetadataValue(context, item, fieldMapping.getTitleField()) + .map(MetadataValue::getValue); + } + + /** + * Take the work title from the configured metadata field of the given item + * (orcid.mapping.work.sub-title), if any. + */ + private Optional getSubTitle(Context context, Item item) { + return getMetadataValue(context, item, fieldMapping.getSubTitleField()) + .map(MetadataValue::getValue) + .map(Subtitle::new); + } + + private PublicationDate getPublicationDate(Context context, Item item) { + return getMetadataValue(context, item, fieldMapping.getPublicationDateField()) + .flatMap(orcidCommonObjectFactory::createFuzzyDate) + .map(PublicationDate::new) + .orElse(null); + } + + /** + * Creates an instance of ExternalIDs from the metadata values of the given + * item, using the orcid.mapping.funding.external-ids configuration. + */ + private ExternalIDs getWorkExternalIds(Context context, Item item) { + ExternalIDs externalIdentifiers = new ExternalIDs(); + externalIdentifiers.getExternalIdentifier().addAll(getWorkSelfExternalIds(context, item)); + externalIdentifiers.getExternalIdentifier().addAll(getWorkFundedByExternalIds(context, item)); + return externalIdentifiers; + } + + /** + * Creates a list of ExternalID, one for orcid.mapping.funding.external-ids + * value, taking the values from the given item. + */ + private List getWorkSelfExternalIds(Context context, Item item) { + + List selfExternalIds = new ArrayList(); + + Map externalIdentifierFields = fieldMapping.getExternalIdentifierFields(); + + if (externalIdentifierFields.containsKey(SIMPLE_HANDLE_PLACEHOLDER)) { + String handleType = externalIdentifierFields.get(SIMPLE_HANDLE_PLACEHOLDER); + selfExternalIds.add(getExternalId(handleType, item.getHandle(), SELF)); + } + + getMetadataValues(context, item, externalIdentifierFields.keySet()).stream() + .map(this::getSelfExternalId) + .forEach(selfExternalIds::add); + + return selfExternalIds; + } + + /** + * Creates an instance of ExternalID taking the value from the given + * metadataValue. The type of the ExternalID is calculated using the + * orcid.mapping.funding.external-ids configuration. The relationship of the + * ExternalID is SELF. + */ + private ExternalID getSelfExternalId(MetadataValue metadataValue) { + Map externalIdentifierFields = fieldMapping.getExternalIdentifierFields(); + String metadataField = metadataValue.getMetadataField().toString('.'); + return getExternalId(externalIdentifierFields.get(metadataField), metadataValue.getValue(), SELF); + } + + private List getWorkFundedByExternalIds(Context context, Item item) { + + if (isBlank(fieldMapping.getFundingExternalIdType())) { + return Collections.emptyList(); + } + + return getMetadataValues(context, item, fieldMapping.getFundingField()).stream() + .map(metadataValue -> getWorkFundedByExternalId(context, item, metadataValue)) + .flatMap(Optional::stream) + .collect(Collectors.toList()); + } + + private Optional getWorkFundedByExternalId(Context context, Item work, MetadataValue fundingMetadata) { + return getFundedByExternalIdFromFunding(context, fundingMetadata) + .or(() -> getFundedByExternalIdFromWork(context, work, fundingMetadata.getPlace())); + } + + private Optional getFundedByExternalIdFromFunding(Context context, MetadataValue fundingMetadata) { + + if (isAuthoritySet(fundingMetadata.getAuthority())) { + return findItemById(context, UUIDUtils.fromString(fundingMetadata.getAuthority())) + .map(funding -> getFundingExternalId(context, funding)); + } + + return Optional.empty(); + } + + private Optional getFundedByExternalIdFromWork(Context context, Item work, int fundingPlace) { + List externalIdValues = getMetadataValues(context, work, fieldMapping.getFundingExternalId()); + + if (externalIdValues.size() > fundingPlace && isNotPlaceholder(externalIdValues.get(fundingPlace))) { + String value = externalIdValues.get(fundingPlace).getValue(); + return Optional.of(getExternalId(fieldMapping.getFundingExternalIdType(), value, FUNDED_BY)); + } + + return Optional.empty(); + } + + private ExternalID getFundingExternalId(Context context, Item funding) { + + String externalIdValue = getMetadataValue(context, funding, fieldMapping.getFundingEntityExternalId()) + .map(MetadataValue::getValue) + .orElse(null); + + if (externalIdValue == null) { + return null; + } + + Optional fundingUrl = getMetadataValue(context, funding, fieldMapping.getFundingUrlField()) + .map(fundingUrlMetadata -> new Url(fundingUrlMetadata.getValue())) + .or(() -> orcidCommonObjectFactory.createUrl(context, funding)); + + ExternalID externalId = getExternalId(fieldMapping.getFundingExternalIdType(), externalIdValue, FUNDED_BY); + fundingUrl.ifPresent(externalId::setUrl); + return externalId; + } + + private boolean isAuthoritySet(String authority) { + return isNotBlank(authority) && !StringUtils.startsWith(authority, AuthorityValueService.REFERENCE); + } + + /** + * Creates an instance of ExternalID with the given type, value and + * relationship. + */ + private ExternalID getExternalId(String type, String value, Relationship relationship) { + ExternalID externalID = new ExternalID(); + externalID.setType(type); + externalID.setValue(value); + externalID.setRelationship(relationship); + return externalID; + } + + /** + * Creates an instance of WorkType from the given item, taking the value fom the + * configured metadata field (orcid.mapping.work.type). + */ + private WorkType getWorkType(Context context, Item item) { + return getMetadataValue(context, item, fieldMapping.getTypeField()) + .map(MetadataValue::getValue) + .map(type -> fieldMapping.convertType(type)) + .flatMap(this::getWorkType) + .orElse(WorkType.UNDEFINED); + } + + /** + * Creates an instance of WorkType from the given workType value, if valid. + */ + private Optional getWorkType(String workType) { + try { + return Optional.ofNullable(WorkType.fromValue(workType)); + } catch (IllegalArgumentException ex) { + LOGGER.warn("The type {} is not valid for ORCID works", workType); + return Optional.empty(); + } + } + + private Citation getWorkCitation(Context context, Item item) { + + CSLItemDataCrosswalk citationCrosswalk = getCitationCrosswalk(); + + if (citationCrosswalk == null || !citationCrosswalk.canDisseminate(context, item)) { + return null; + } + + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { + citationCrosswalk.disseminate(context, item, out); + return new Citation(out.toString(), fieldMapping.getCitationType()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private CSLItemDataCrosswalk getCitationCrosswalk() { + CitationType citationType = fieldMapping.getCitationType(); + return citationType != null ? fieldMapping.getCitationCrosswalks().get(citationType.value()) : null; + } + + private String getShortDescription(Context context, Item item) { + return getMetadataValue(context, item, fieldMapping.getShortDescriptionField()) + .map(MetadataValue::getValue) + .orElse(null); + } + + private String getLanguageCode(Context context, Item item) { + return getMetadataValue(context, item, fieldMapping.getLanguageField()) + .map(MetadataValue::getValue) + .map(language -> fieldMapping.convertLanguage(language)) + .filter(language -> isValidLanguage(language)) + .orElse(null); + } + + private boolean isValidLanguage(String language) { + + if (isBlank(language)) { + return false; + } + + boolean isValid = EnumUtils.isValidEnum(LanguageCode.class, language); + if (!isValid) { + LOGGER.warn("The language {} is not a valid language code for ORCID works", language); + } + return isValid; + } + + private Url getUrl(Context context, Item item) { + return orcidCommonObjectFactory.createUrl(context, item).orElse(null); + } + + private List getMetadataValues(Context context, Item item, String metadataField) { + if (isBlank(metadataField)) { + return Collections.emptyList(); + } + return itemService.getMetadataByMetadataString(item, metadataField); + } + + private boolean isNotPlaceholder(MetadataValue metadata) { + return metadata != null && metadata.getValue() != null + && !metadata.getValue().equals(PLACEHOLDER_PARENT_METADATA_VALUE); + } + + private List getMetadataValues(Context context, Item item, Collection metadataFields) { + return metadataFields.stream() + .flatMap(metadataField -> itemService.getMetadataByMetadataString(item, metadataField).stream()) + .collect(Collectors.toList()); + } + + private Optional findItemById(Context context, UUID id) { + try { + return Optional.ofNullable(itemService.find(context, id)); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + private Optional getMetadataValue(Context context, Item item, String metadataField) { + + if (isBlank(metadataField)) { + return Optional.empty(); + } + + return itemService.getMetadataByMetadataString(item, metadataField).stream() + .filter(metadataValue -> isNotBlank(metadataValue.getValue())) + .findFirst(); + } + + public void setFieldMapping(OrcidPatentWorkFieldMapping fieldMapping) { + this.fieldMapping = fieldMapping; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidProductWorkFactory.java b/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidProductWorkFactory.java new file mode 100644 index 000000000000..2636592cfae4 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidProductWorkFactory.java @@ -0,0 +1,398 @@ +/** + * 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.orcid.model.factory.impl; + +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.dspace.core.CrisConstants.PLACEHOLDER_PARENT_METADATA_VALUE; +import static org.orcid.jaxb.model.common.Relationship.FUNDED_BY; +import static org.orcid.jaxb.model.common.Relationship.SELF; + +import java.io.ByteArrayOutputStream; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.EnumUtils; +import org.apache.commons.lang3.StringUtils; +import org.dspace.authority.service.AuthorityValueService; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.integration.crosswalks.CSLItemDataCrosswalk; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.orcid.model.OrcidEntityType; +import org.dspace.orcid.model.OrcidProductWorkFieldMapping; +import org.dspace.orcid.model.factory.OrcidCommonObjectFactory; +import org.dspace.orcid.model.factory.OrcidEntityFactory; +import org.dspace.util.UUIDUtils; +import org.orcid.jaxb.model.common.CitationType; +import org.orcid.jaxb.model.common.ContributorRole; +import org.orcid.jaxb.model.common.LanguageCode; +import org.orcid.jaxb.model.common.Relationship; +import org.orcid.jaxb.model.common.WorkType; +import org.orcid.jaxb.model.v3.release.common.Contributor; +import org.orcid.jaxb.model.v3.release.common.PublicationDate; +import org.orcid.jaxb.model.v3.release.common.Subtitle; +import org.orcid.jaxb.model.v3.release.common.Title; +import org.orcid.jaxb.model.v3.release.common.Url; +import org.orcid.jaxb.model.v3.release.record.Activity; +import org.orcid.jaxb.model.v3.release.record.Citation; +import org.orcid.jaxb.model.v3.release.record.ExternalID; +import org.orcid.jaxb.model.v3.release.record.ExternalIDs; +import org.orcid.jaxb.model.v3.release.record.Work; +import org.orcid.jaxb.model.v3.release.record.WorkContributors; +import org.orcid.jaxb.model.v3.release.record.WorkTitle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of {@link OrcidEntityFactory} that creates instances of + * {@link Work}. Copy of {@link OrcidWorkFactory} + * Adapted for Product Entity with own mapping in {@link org.dspace.orcid.model.OrcidProductWorkFieldMapping} + * + */ +public class OrcidProductWorkFactory implements OrcidEntityFactory { + + private static final Logger LOGGER = LoggerFactory.getLogger(OrcidProductWorkFactory.class); + + @Autowired + private ItemService itemService; + + @Autowired + private OrcidCommonObjectFactory orcidCommonObjectFactory; + + private OrcidProductWorkFieldMapping fieldMapping; + + @Override + public OrcidEntityType getEntityType() { + return OrcidEntityType.PRODUCT; + } + + @Override + public Activity createOrcidObject(Context context, Item item) { + Work work = new Work(); + work.setJournalTitle(getJournalTitle(context, item)); + work.setWorkContributors(getWorkContributors(context, item)); + work.setWorkTitle(getWorkTitle(context, item)); + work.setPublicationDate(getPublicationDate(context, item)); + work.setWorkExternalIdentifiers(getWorkExternalIds(context, item)); + work.setWorkType(getWorkType(context, item)); + work.setWorkCitation(getWorkCitation(context, item)); + work.setShortDescription(getShortDescription(context, item)); + work.setLanguageCode(getLanguageCode(context, item)); + work.setUrl(getUrl(context, item)); + return work; + } + + private Title getJournalTitle(Context context, Item item) { + return getMetadataValue(context, item, fieldMapping.getJournalTitleField()) + .map(metadataValue -> new Title(metadataValue.getValue())) + .orElse(null); + } + + private WorkContributors getWorkContributors(Context context, Item item) { + Map contributorFields = fieldMapping.getContributorFields(); + List contributors = getMetadataValues(context, item, contributorFields.keySet()).stream() + .map(metadataValue -> getContributor(context, metadataValue)) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + return new WorkContributors(contributors); + } + + private Optional getContributor(Context context, MetadataValue metadataValue) { + Map contributorFields = fieldMapping.getContributorFields(); + ContributorRole role = contributorFields.get(metadataValue.getMetadataField().toString('.')); + return orcidCommonObjectFactory.createContributor(context, metadataValue, role); + } + + /** + * Create an instance of WorkTitle from the given item. + */ + private WorkTitle getWorkTitle(Context context, Item item) { + Optional workTitleValue = getWorkTitleValue(context, item); + if (workTitleValue.isEmpty()) { + return null; + } + + WorkTitle workTitle = new WorkTitle(); + workTitle.setTitle(new Title(workTitleValue.get())); + getSubTitle(context, item).ifPresent(workTitle::setSubtitle); + return workTitle; + } + + /** + * Take the work title from the configured metadata field of the given item + * (orcid.mapping.work.title), if any. + */ + private Optional getWorkTitleValue(Context context, Item item) { + return getMetadataValue(context, item, fieldMapping.getTitleField()) + .map(MetadataValue::getValue); + } + + /** + * Take the work title from the configured metadata field of the given item + * (orcid.mapping.work.sub-title), if any. + */ + private Optional getSubTitle(Context context, Item item) { + return getMetadataValue(context, item, fieldMapping.getSubTitleField()) + .map(MetadataValue::getValue) + .map(Subtitle::new); + } + + private PublicationDate getPublicationDate(Context context, Item item) { + return getMetadataValue(context, item, fieldMapping.getPublicationDateField()) + .flatMap(orcidCommonObjectFactory::createFuzzyDate) + .map(PublicationDate::new) + .orElse(null); + } + + /** + * Creates an instance of ExternalIDs from the metadata values of the given + * item, using the orcid.mapping.funding.external-ids configuration. + */ + private ExternalIDs getWorkExternalIds(Context context, Item item) { + ExternalIDs externalIdentifiers = new ExternalIDs(); + externalIdentifiers.getExternalIdentifier().addAll(getWorkSelfExternalIds(context, item)); + externalIdentifiers.getExternalIdentifier().addAll(getWorkFundedByExternalIds(context, item)); + return externalIdentifiers; + } + + /** + * Creates a list of ExternalID, one for orcid.mapping.funding.external-ids + * value, taking the values from the given item. + */ + private List getWorkSelfExternalIds(Context context, Item item) { + + List selfExternalIds = new ArrayList(); + + Map externalIdentifierFields = fieldMapping.getExternalIdentifierFields(); + + if (externalIdentifierFields.containsKey(SIMPLE_HANDLE_PLACEHOLDER)) { + String handleType = externalIdentifierFields.get(SIMPLE_HANDLE_PLACEHOLDER); + selfExternalIds.add(getExternalId(handleType, item.getHandle(), SELF)); + } + + getMetadataValues(context, item, externalIdentifierFields.keySet()).stream() + .map(this::getSelfExternalId) + .forEach(selfExternalIds::add); + + return selfExternalIds; + } + + /** + * Creates an instance of ExternalID taking the value from the given + * metadataValue. The type of the ExternalID is calculated using the + * orcid.mapping.funding.external-ids configuration. The relationship of the + * ExternalID is SELF. + */ + private ExternalID getSelfExternalId(MetadataValue metadataValue) { + Map externalIdentifierFields = fieldMapping.getExternalIdentifierFields(); + String metadataField = metadataValue.getMetadataField().toString('.'); + return getExternalId(externalIdentifierFields.get(metadataField), metadataValue.getValue(), SELF); + } + + private List getWorkFundedByExternalIds(Context context, Item item) { + + if (isBlank(fieldMapping.getFundingExternalIdType())) { + return Collections.emptyList(); + } + + return getMetadataValues(context, item, fieldMapping.getFundingField()).stream() + .map(metadataValue -> getWorkFundedByExternalId(context, item, metadataValue)) + .flatMap(Optional::stream) + .collect(Collectors.toList()); + } + + private Optional getWorkFundedByExternalId(Context context, Item work, MetadataValue fundingMetadata) { + return getFundedByExternalIdFromFunding(context, fundingMetadata) + .or(() -> getFundedByExternalIdFromWork(context, work, fundingMetadata.getPlace())); + } + + private Optional getFundedByExternalIdFromFunding(Context context, MetadataValue fundingMetadata) { + + if (isAuthoritySet(fundingMetadata.getAuthority())) { + return findItemById(context, UUIDUtils.fromString(fundingMetadata.getAuthority())) + .map(funding -> getFundingExternalId(context, funding)); + } + + return Optional.empty(); + } + + private Optional getFundedByExternalIdFromWork(Context context, Item work, int fundingPlace) { + List externalIdValues = getMetadataValues(context, work, fieldMapping.getFundingExternalId()); + + if (externalIdValues.size() > fundingPlace && isNotPlaceholder(externalIdValues.get(fundingPlace))) { + String value = externalIdValues.get(fundingPlace).getValue(); + return Optional.of(getExternalId(fieldMapping.getFundingExternalIdType(), value, FUNDED_BY)); + } + + return Optional.empty(); + } + + private ExternalID getFundingExternalId(Context context, Item funding) { + + String externalIdValue = getMetadataValue(context, funding, fieldMapping.getFundingEntityExternalId()) + .map(MetadataValue::getValue) + .orElse(null); + + if (externalIdValue == null) { + return null; + } + + Optional fundingUrl = getMetadataValue(context, funding, fieldMapping.getFundingUrlField()) + .map(fundingUrlMetadata -> new Url(fundingUrlMetadata.getValue())) + .or(() -> orcidCommonObjectFactory.createUrl(context, funding)); + + ExternalID externalId = getExternalId(fieldMapping.getFundingExternalIdType(), externalIdValue, FUNDED_BY); + fundingUrl.ifPresent(externalId::setUrl); + return externalId; + } + + private boolean isAuthoritySet(String authority) { + return isNotBlank(authority) && !StringUtils.startsWith(authority, AuthorityValueService.REFERENCE); + } + + /** + * Creates an instance of ExternalID with the given type, value and + * relationship. + */ + private ExternalID getExternalId(String type, String value, Relationship relationship) { + ExternalID externalID = new ExternalID(); + externalID.setType(type); + externalID.setValue(value); + externalID.setRelationship(relationship); + return externalID; + } + + /** + * Creates an instance of WorkType from the given item, taking the value fom the + * configured metadata field (orcid.mapping.work.type). + */ + private WorkType getWorkType(Context context, Item item) { + return getMetadataValue(context, item, fieldMapping.getTypeField()) + .map(MetadataValue::getValue) + .map(type -> fieldMapping.convertType(type)) + .flatMap(this::getWorkType) + .orElse(WorkType.UNDEFINED); + } + + /** + * Creates an instance of WorkType from the given workType value, if valid. + */ + private Optional getWorkType(String workType) { + try { + return Optional.ofNullable(WorkType.fromValue(workType)); + } catch (IllegalArgumentException ex) { + LOGGER.warn("The type {} is not valid for ORCID works", workType); + return Optional.empty(); + } + } + + private Citation getWorkCitation(Context context, Item item) { + + CSLItemDataCrosswalk citationCrosswalk = getCitationCrosswalk(); + + if (citationCrosswalk == null || !citationCrosswalk.canDisseminate(context, item)) { + return null; + } + + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { + citationCrosswalk.disseminate(context, item, out); + return new Citation(out.toString(), fieldMapping.getCitationType()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private CSLItemDataCrosswalk getCitationCrosswalk() { + CitationType citationType = fieldMapping.getCitationType(); + return citationType != null ? fieldMapping.getCitationCrosswalks().get(citationType.value()) : null; + } + + private String getShortDescription(Context context, Item item) { + return getMetadataValue(context, item, fieldMapping.getShortDescriptionField()) + .map(MetadataValue::getValue) + .orElse(null); + } + + private String getLanguageCode(Context context, Item item) { + return getMetadataValue(context, item, fieldMapping.getLanguageField()) + .map(MetadataValue::getValue) + .map(language -> fieldMapping.convertLanguage(language)) + .filter(language -> isValidLanguage(language)) + .orElse(null); + } + + private boolean isValidLanguage(String language) { + + if (isBlank(language)) { + return false; + } + + boolean isValid = EnumUtils.isValidEnum(LanguageCode.class, language); + if (!isValid) { + LOGGER.warn("The language {} is not a valid language code for ORCID works", language); + } + return isValid; + } + + private Url getUrl(Context context, Item item) { + return orcidCommonObjectFactory.createUrl(context, item).orElse(null); + } + + private List getMetadataValues(Context context, Item item, String metadataField) { + if (isBlank(metadataField)) { + return Collections.emptyList(); + } + return itemService.getMetadataByMetadataString(item, metadataField); + } + + private boolean isNotPlaceholder(MetadataValue metadata) { + return metadata != null && metadata.getValue() != null + && !metadata.getValue().equals(PLACEHOLDER_PARENT_METADATA_VALUE); + } + + private List getMetadataValues(Context context, Item item, Collection metadataFields) { + return metadataFields.stream() + .flatMap(metadataField -> itemService.getMetadataByMetadataString(item, metadataField).stream()) + .collect(Collectors.toList()); + } + + private Optional findItemById(Context context, UUID id) { + try { + return Optional.ofNullable(itemService.find(context, id)); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + private Optional getMetadataValue(Context context, Item item, String metadataField) { + + if (isBlank(metadataField)) { + return Optional.empty(); + } + + return itemService.getMetadataByMetadataString(item, metadataField).stream() + .filter(metadataValue -> isNotBlank(metadataValue.getValue())) + .findFirst(); + } + + public void setFieldMapping(OrcidProductWorkFieldMapping fieldMapping) { + this.fieldMapping = fieldMapping; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java index 2ea0a52d6e34..5f9693fee69b 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java +++ b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java @@ -64,6 +64,10 @@ private void setHandler(DSpaceRunnableHandler dSpaceRunnableHandler) { this.handler = dSpaceRunnableHandler; } + public DSpaceRunnableHandler getHandler() { + return this.handler; + } + /** * This method sets the appropriate DSpaceRunnableHandler depending on where it was ran from and it parses * the arguments given to the script diff --git a/dspace-api/src/main/java/org/dspace/subscriptions/StatisticsGenerator.java b/dspace-api/src/main/java/org/dspace/subscriptions/StatisticsGenerator.java index 43ff6b71d4b5..c6549f744904 100644 --- a/dspace-api/src/main/java/org/dspace/subscriptions/StatisticsGenerator.java +++ b/dspace-api/src/main/java/org/dspace/subscriptions/StatisticsGenerator.java @@ -11,6 +11,7 @@ import java.io.File; import java.io.FileOutputStream; import java.util.List; +import java.util.Locale; import java.util.Objects; import org.apache.logging.log4j.LogManager; @@ -25,9 +26,8 @@ import org.dspace.app.metrics.CrisMetrics; import org.dspace.core.Context; import org.dspace.core.Email; +import org.dspace.core.I18nUtil; import org.dspace.eperson.EPerson; -import org.dspace.services.ConfigurationService; -import org.springframework.beans.factory.annotation.Autowired; /** @@ -40,23 +40,17 @@ public class StatisticsGenerator { private static final Logger log = LogManager.getLogger(StatisticsGenerator.class); - @Autowired - private ConfigurationService configurationService; - public void notifyForSubscriptions(Context c, EPerson ePerson, List crisMetricsList) { try { // send the notification to the user - if (Objects.nonNull(ePerson) && !crisMetricsList.isEmpty()) { - Email email = new Email(); - String name = configurationService.getProperty("dspace.name"); - File attachment = generateExcel(crisMetricsList, c); - email.addAttachment(attachment, "subscriptions.xlsx"); - email.setSubject(name + ": Statistics of records which you are subscribed"); - email.setContent("intro", - "This automatic email is sent by " + name + " based on the subscribed statistics updates.\n\n" + - "See additional details in the file attached."); - email.send(); + if (Objects.isNull(ePerson) || crisMetricsList.isEmpty()) { + return; } + Locale supportedLocale = I18nUtil.getEPersonLocale(ePerson); + Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "subscriptions_statistics")); + email.addRecipient(ePerson.getEmail()); + email.addAttachment(generateExcel(crisMetricsList, c), "subscriptions.xlsx"); + email.send(); } catch (Exception ex) { // log this email error log.warn("cannot email user" + " eperson_id" + ePerson.getID() diff --git a/dspace-api/src/main/java/org/dspace/validation/MetadataValidator.java b/dspace-api/src/main/java/org/dspace/validation/MetadataValidator.java index d53b939ee44a..3be35da792c6 100644 --- a/dspace-api/src/main/java/org/dspace/validation/MetadataValidator.java +++ b/dspace-api/src/main/java/org/dspace/validation/MetadataValidator.java @@ -34,6 +34,7 @@ import org.dspace.core.exception.SQLRuntimeException; import org.dspace.services.ConfigurationService; import org.dspace.validation.model.ValidationError; +import org.dspace.workflow.WorkflowItem; /** * Execute three validation check on fields validation: - mandatory metadata @@ -100,12 +101,13 @@ public List validate(Context context, InProgressSubmission o } else { validateMetadataValues(obj.getCollection(), mdv, input, config, isAuthorityControlled, fieldKey, errors); - if (mdv.size() > 0 && input.isVisible(DCInput.SUBMISSION_SCOPE)) { + if (mdv.size() > 0 && (input.isVisible(DCInput.SUBMISSION_SCOPE) || + input.isVisible(DCInput.WORKFLOW_SCOPE))) { foundResult = true; } } } - if (input.isRequired() && ! foundResult) { + if (input.isRequired() && !foundResult) { // for this required qualdrop no value was found, add to the list of error fields addError(errors, ERROR_VALIDATION_REQUIRED, "/" + OPERATION_PATH_SECTIONS + "/" + config.getId() + "/" + @@ -139,8 +141,10 @@ public List validate(Context context, InProgressSubmission o } validateMetadataValues(obj.getCollection(), mdv, input, config, isAuthorityControlled, fieldKey, errors); - if ((input.isRequired() && mdv.size() == 0) && input.isVisible(DCInput.SUBMISSION_SCOPE) - && !valuesRemoved) { + if ((input.isRequired() && mdv.size() == 0) + && (input.isVisible(DCInput.SUBMISSION_SCOPE) + || (obj instanceof WorkflowItem && input.isVisible(DCInput.WORKFLOW_SCOPE))) + && !valuesRemoved) { // Is the input required for *this* type? In other words, are we looking at a required // input that is also allowed for this document type if (input.isAllowedFor(documentType)) { diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.03.07__create_table_items_for_update.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.03.07__create_table_items_for_update.sql new file mode 100644 index 000000000000..b1dfe8afc79a --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.03.07__create_table_items_for_update.sql @@ -0,0 +1,18 @@ +-- +-- 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 TABLE itemupdate_metadata_enhancement +----------------------------------------------------------------------------------- + +CREATE TABLE itemupdate_metadata_enhancement +( + uuid UUID NOT NULL PRIMARY KEY, + date_queued TIMESTAMP NOT NULL +); +CREATE INDEX idx_date_queued ON itemupdate_metadata_enhancement(date_queued); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.04.09__create_idx_optimize_item_poller.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.04.09__create_idx_optimize_item_poller.sql new file mode 100644 index 000000000000..cda4d33018d4 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.04.09__create_idx_optimize_item_poller.sql @@ -0,0 +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/ +-- + +----------------------------------------------------------------------------------- +-- Create INDEXES to optimize exact query over the metadatavalue +----------------------------------------------------------------------------------- + +-- we cannot create the idx related to the text_value substring in H2 as index on +-- expression are not supported, see https://github.com/h2database/h2database/issues/3535 +-- CREATE INDEX idx_text_value_hash ON metadatavalue (SUBSTRING(text_value,1,36)); +CREATE INDEX idx_authority_hash ON metadatavalue (authority); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.03.07__create_table_items_for_update.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.03.07__create_table_items_for_update.sql new file mode 100644 index 000000000000..b1dfe8afc79a --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.03.07__create_table_items_for_update.sql @@ -0,0 +1,18 @@ +-- +-- 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 TABLE itemupdate_metadata_enhancement +----------------------------------------------------------------------------------- + +CREATE TABLE itemupdate_metadata_enhancement +( + uuid UUID NOT NULL PRIMARY KEY, + date_queued TIMESTAMP NOT NULL +); +CREATE INDEX idx_date_queued ON itemupdate_metadata_enhancement(date_queued); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.04.09__create_idx_optimize_item_poller.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.04.09__create_idx_optimize_item_poller.sql new file mode 100644 index 000000000000..a3dbfb01d025 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.04.09__create_idx_optimize_item_poller.sql @@ -0,0 +1,14 @@ +-- +-- 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 INDEXES to optimize exact query over the metadatavalue +----------------------------------------------------------------------------------- + +CREATE INDEX idx_text_value_hash ON metadatavalue (substring(text_value,1,36)); +CREATE INDEX idx_authority_hash ON metadatavalue (authority); \ No newline at end of file diff --git a/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml index d94d1145ee45..8a951d26b9e2 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml @@ -295,6 +295,11 @@ org.dspace.app.rest.submit.step.DescribeStep submission-form + + submit.progressbar.describe.funding + org.dspace.app.rest.submit.step.DescribeStep + submission-form + @@ -479,6 +484,13 @@ + + + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index b02b9fd15508..e4a3de5f9a68 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -227,4 +227,6 @@ authority.controlled.dspace.object.owner = true # force the event system to work synchronously during test system-event.thread.size = 0 -vocabulary.plugin.srsc-noauthority.authority.store = false \ No newline at end of file +vocabulary.plugin.srsc-noauthority.authority.store = false +# disable the item enhancer poller during test +related-item-enhancer-poller.enabled = false diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/edititem-service.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/edititem-service.xml index 787bb5ebdd5a..18a84e44b2fc 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/edititem-service.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/edititem-service.xml @@ -145,6 +145,19 @@ + + + + + + + ADMIN + + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/extra-metadata-enhancers-for-test.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/extra-metadata-enhancers-for-test.xml index 0311d8a26aa5..a713d3acbcc6 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/extra-metadata-enhancers-for-test.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/extra-metadata-enhancers-for-test.xml @@ -21,7 +21,11 @@ - + + + TestEntity + + dc.contributor.author diff --git a/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml b/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml index 15c3574951d9..d88a7555fa4d 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml @@ -2360,6 +2360,79 @@ it, please enter the types and the actual numbers or codes. + +
+ + + dc + title + + onebox + false + You must enter the oganization name. + + + +
+ +
+ + + dc + title + + onebox + false + You must enter the project title + + + + + + oairecerif + funder + + inline-group + true + You must enter financing information + + + +
+ +
+ + + oairecerif + funder + + onebox + false + You must enter the funder + + + + + oairecerif + amount + + onebox + false + + + + + oairecerif + amount + currency + + dropdown + false + + + + +
@@ -2803,6 +2876,137 @@ it, please enter the types and the actual numbers or codes. + + + Swiss franc + Swiss franc + + + Euro + Euro + + + US dollar + US dollar + + + Albanian lek + Albanian lek + + + Armenian dram + Armenian dram + + + Azerbaijan manat + Azerbaijan manat + + + Belarusian ruble + Belarusian ruble + + + Bosnia and Herzegovina convertible mark + Bosnia and Herzegovina convertible mark + + + Bulgarian lev + Bulgarian lev + + + Croatian kuna + Croatian kuna + + + Czech koruna + Czech koruna + + + Danish krone + Danish krone + + + Faroese krona + Faroese krona + + + Georgian lari + Georgian lari + + + Gibraltar pound + Gibraltar pound + + + Guernsey pound + Guernsey pound + + + Hungarian forint + Hungarian forint + + + Icelandic krona + Icelandic krona + + + Manx pound + Manx pound + + + Jersey pound + Jersey pound + + + Kazakhstani tenge + Kazakhstani tenge + + + Moldovan leu + Moldovan leu + + + Macedonian denar + Macedonian denar + + + Norwegian krone + Norwegian krone + + + Polish zloty + Polish zloty + + + Romanian leu + Romanian leu + + + Russian ruble + Russian ruble + + + Serbian dinar + Serbian dinar + + + Swedish krona + Swedish krona + + + Turkish lira + Turkish lira + + + Ukrainian hryvnia + Ukrainian hryvnia + + + Pound sterling + Pound sterling + + + diff --git a/dspace-api/src/test/java/org/dspace/app/matcher/MetadataValueMatcher.java b/dspace-api/src/test/java/org/dspace/app/matcher/MetadataValueMatcher.java index 55ceb779ba0b..1566f965a8d1 100644 --- a/dspace-api/src/test/java/org/dspace/app/matcher/MetadataValueMatcher.java +++ b/dspace-api/src/test/java/org/dspace/app/matcher/MetadataValueMatcher.java @@ -96,6 +96,10 @@ public static MetadataValueMatcher withNoPlace(String field, String value) { return with(field, value, null, null, null, -1); } + public static MetadataValueMatcher withNoPlace(String field, String value, String authority) { + return with(field, value, null, authority, null, 600); + } + public static MetadataValueMatcher withSecurity(String field, String value, Integer securityLevel) { return with(field, value, null, null, 0, -1, securityLevel); } diff --git a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java index b9eedd2fcf42..ba4c529eb28d 100644 --- a/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/ItemBuilder.java @@ -593,6 +593,22 @@ public ItemBuilder withOrcidSynchronizationFundingsPreference(String value) { return setMetadataSingleValue(item, "dspace", "orcid", "sync-fundings", value); } + public ItemBuilder withOrcidSynchronizationProductsPreference(OrcidEntitySyncPreference value) { + return withOrcidSynchronizationProductsPreference(value.name()); + } + + public ItemBuilder withOrcidSynchronizationProductsPreference(String value) { + return setMetadataSingleValue(item, "dspace", "orcid", "sync-products", value); + } + + public ItemBuilder withOrcidSynchronizationPatentsPreference(OrcidEntitySyncPreference value) { + return withOrcidSynchronizationPatentsPreference(value.name()); + } + + public ItemBuilder withOrcidSynchronizationPatentsPreference(String value) { + return setMetadataSingleValue(item, "dspace", "orcid", "sync-patents", value); + } + public ItemBuilder withOrcidSynchronizationProfilePreference(OrcidProfileSyncPreference value) { return withOrcidSynchronizationProfilePreference(value.name()); } diff --git a/dspace-api/src/test/java/org/dspace/content/enhancer/consumer/ItemEnhancerConsumerIT.java b/dspace-api/src/test/java/org/dspace/content/enhancer/consumer/ItemEnhancerConsumerIT.java index b2b34c1074fb..bd93c506d858 100644 --- a/dspace-api/src/test/java/org/dspace/content/enhancer/consumer/ItemEnhancerConsumerIT.java +++ b/dspace-api/src/test/java/org/dspace/content/enhancer/consumer/ItemEnhancerConsumerIT.java @@ -379,8 +379,8 @@ public void testEnhancementAfterItemUpdate() throws Exception { assertThat(getMetadataValues(publication, "cris.virtual.orcid"), contains( with("cris.virtual.orcid", "0000-0000-1111-2222"))); - assertThat(getMetadataValues(publication, "cris.virtualsource.orcid"), contains( - with("cris.virtualsource.orcid", personId))); + assertThat(getMetadataValues(publication, "cris.virtualsource.orcid"), contains( + with("cris.virtualsource.orcid", personId))); } diff --git a/dspace-api/src/test/java/org/dspace/content/enhancer/script/ItemEnhancerScriptIT.java b/dspace-api/src/test/java/org/dspace/content/enhancer/script/ItemEnhancerScriptIT.java index 6d67c67a10ba..57d296f53fe9 100644 --- a/dspace-api/src/test/java/org/dspace/content/enhancer/script/ItemEnhancerScriptIT.java +++ b/dspace-api/src/test/java/org/dspace/content/enhancer/script/ItemEnhancerScriptIT.java @@ -295,25 +295,26 @@ public void testItemEnhancementWithForce() throws Exception { context.turnOffAuthorisationSystem(); - Item firstAuthor = ItemBuilder.createItem(context, collection) + Item editor = ItemBuilder.createItem(context, collection) .withTitle("Walter White") .withPersonMainAffiliation("4Science") .build(); - String firstAuthorId = firstAuthor.getID().toString(); + String editorId = editor.getID().toString(); - Item secondAuthor = ItemBuilder.createItem(context, collection) + Item author = ItemBuilder.createItem(context, collection) .withTitle("Jesse Pinkman") .withPersonMainAffiliation("Company") + .withPersonMainAffiliation("Another Company") .build(); - String secondAuthorId = secondAuthor.getID().toString(); + String authorId = author.getID().toString(); Item publication = ItemBuilder.createItem(context, collection) .withTitle("Test publication 2 ") .withEntityType("Publication") - .withEditor("Walter White", firstAuthorId) - .withAuthor("Jesse Pinkman", secondAuthorId) + .withEditor("Walter White", editorId) + .withAuthor("Jesse Pinkman", authorId) .build(); context.commit(); @@ -329,22 +330,24 @@ public void testItemEnhancementWithForce() throws Exception { publication = reload(publication); - assertThat(getMetadataValues(publication, "cris.virtual.department"), hasSize(2)); - assertThat(getMetadataValues(publication, "cris.virtualsource.department"), hasSize(2)); + assertThat(getMetadataValues(publication, "cris.virtual.department"), hasSize(3)); + assertThat(getMetadataValues(publication, "cris.virtualsource.department"), hasSize(3)); assertThat(getMetadataValues(publication, "cris.virtual.department"), containsInAnyOrder( withNoPlace("cris.virtual.department", "4Science"), + withNoPlace("cris.virtual.department", "Another Company"), withNoPlace("cris.virtual.department", "Company"))); assertThat(getMetadataValues(publication, "cris.virtualsource.department"), containsInAnyOrder( - withNoPlace("cris.virtualsource.department", firstAuthorId), - withNoPlace("cris.virtualsource.department", secondAuthorId))); + withNoPlace("cris.virtualsource.department", editorId), + withNoPlace("cris.virtualsource.department", authorId), + withNoPlace("cris.virtualsource.department", authorId))); context.turnOffAuthorisationSystem(); MetadataValue authorToRemove = getMetadataValues(publication, "dc.contributor.author").get(0); itemService.removeMetadataValues(context, publication, List.of(authorToRemove)); - replaceMetadata(firstAuthor, "person", "affiliation", "name", "University"); + replaceMetadata(editor, "person", "affiliation", "name", "University"); context.restoreAuthSystemState(); @@ -357,9 +360,10 @@ public void testItemEnhancementWithForce() throws Exception { assertThat(getMetadataValues(publication, "cris.virtual.department"), hasSize(1)); assertThat(getMetadataValues(publication, "cris.virtualsource.department"), hasSize(1)); - assertThat(publication.getMetadata(), hasItem(with("cris.virtual.department", "University"))); - assertThat(publication.getMetadata(), hasItem(with("cris.virtualsource.department", firstAuthorId))); - + assertThat(getMetadataValues(publication, "cris.virtual.department"), hasItem( + with("cris.virtual.department", "University"))); + assertThat(getMetadataValues(publication, "cris.virtualsource.department"), hasItem( + with("cris.virtualsource.department", editorId))); } @Test diff --git a/dspace-api/src/test/java/org/dspace/discovery/SolrServiceFileInfoPluginTest.java b/dspace-api/src/test/java/org/dspace/discovery/SolrServiceFileInfoPluginTest.java index 923fff4c0cfd..4ce92eeae2fa 100644 --- a/dspace-api/src/test/java/org/dspace/discovery/SolrServiceFileInfoPluginTest.java +++ b/dspace-api/src/test/java/org/dspace/discovery/SolrServiceFileInfoPluginTest.java @@ -7,9 +7,6 @@ */ package org.dspace.discovery; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.not; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; @@ -25,6 +22,7 @@ import org.dspace.content.Bundle; import org.dspace.content.Item; import org.dspace.core.Context; +import org.dspace.discovery.index.adder.IndexAdder; import org.dspace.discovery.indexobject.IndexableItem; import org.junit.Before; import org.junit.Test; @@ -64,13 +62,15 @@ public void shouldHandleNPE() { SolrInputDocument document = spy(solrInputDocument); + IndexAdder simpleSolrIndexAdder = mock(IndexAdder.class); + solrServiceFileInfoPlugin.setSimpleIndexAdder(simpleSolrIndexAdder); + doThrow(new NullPointerException()) .when(document) .addField("original_bundle_filenames", "bitstream1"); solrServiceFileInfoPlugin.additionalIndex(context, indexableItem, document); - verify(document, times(2)).addField(any(), any()); - assertThat(document.getFieldNames(), not(empty())); + verify(simpleSolrIndexAdder, times(2)).add(any(), any(), any()); } } \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java b/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java index a1ebec2197e4..102474981f49 100644 --- a/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java +++ b/dspace-api/src/test/java/org/dspace/orcid/OrcidQueueConsumerIT.java @@ -586,6 +586,76 @@ public void testOrcidQueueRecordCreationForFunding() throws Exception { assertThat(orcidQueueRecords.get(0), equalTo(newOrcidQueueRecords.get(0))); } + @Test + public void testOrcidQueueRecordCreationForProduct() throws Exception { + + context.turnOffAuthorisationSystem(); + + Item profile = ItemBuilder.createItem(context, profileCollection) + .withTitle("Test User") + .withOrcidIdentifier("0000-1111-2222-3333") + .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", eperson) + .withOrcidSynchronizationProductsPreference(ALL) + .build(); + + Collection productCollection = createCollection("Products", "Product"); + + Item product = ItemBuilder.createItem(context, productCollection) + .withTitle("Test product") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + context.restoreAuthSystemState(); + context.commit(); + + List orcidQueueRecords = orcidQueueService.findAll(context); + assertThat(orcidQueueRecords, hasSize(1)); + assertThat(orcidQueueRecords.get(0), matches(profile, product, "Product", null, INSERT)); + + addMetadata(product, "dc", "type", null, "http://purl.org/coar/resource_type/scheme/c_12cc", null); + context.commit(); + + List newOrcidQueueRecords = orcidQueueService.findAll(context); + assertThat(newOrcidQueueRecords, hasSize(1)); + + assertThat(orcidQueueRecords.get(0), equalTo(newOrcidQueueRecords.get(0))); + } + + @Test + public void testOrcidQueueRecordCreationForPatent() throws Exception { + + context.turnOffAuthorisationSystem(); + + Item profile = ItemBuilder.createItem(context, profileCollection) + .withTitle("Test User") + .withOrcidIdentifier("0000-1111-2222-3333") + .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", eperson) + .withOrcidSynchronizationPatentsPreference(ALL) + .build(); + + Collection patentCollection = createCollection("Patents", "Patent"); + + Item patent = ItemBuilder.createItem(context, patentCollection) + .withTitle("Test patent") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + context.restoreAuthSystemState(); + context.commit(); + + List orcidQueueRecords = orcidQueueService.findAll(context); + assertThat(orcidQueueRecords, hasSize(1)); + assertThat(orcidQueueRecords.get(0), matches(profile, patent, "Patent", null, INSERT)); + + addMetadata(patent, "dc", "type", null, "http://purl.org/coar/resource_type/scheme/Z907-YMBB", null); + context.commit(); + + List newOrcidQueueRecords = orcidQueueService.findAll(context); + assertThat(newOrcidQueueRecords, hasSize(1)); + + assertThat(orcidQueueRecords.get(0), equalTo(newOrcidQueueRecords.get(0))); + } + @Test public void testOrcidQueueRecordCreationToUpdateFunding() throws Exception { @@ -618,6 +688,70 @@ public void testOrcidQueueRecordCreationToUpdateFunding() throws Exception { assertThat(orcidQueueRecords.get(0), matches(profile, funding, "Funding", "123456", UPDATE)); } + @Test + public void testOrcidQueueRecordCreationToUpdateProduct() throws Exception { + + context.turnOffAuthorisationSystem(); + + Item profile = ItemBuilder.createItem(context, profileCollection) + .withTitle("Test User") + .withOrcidIdentifier("0000-1111-2222-3333") + .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", eperson) + .withOrcidSynchronizationProductsPreference(ALL) + .build(); + + Collection productCollection = createCollection("Products", "Product"); + + Item product = ItemBuilder.createItem(context, productCollection) + .withTitle("Test product") + .build(); + + createOrcidHistory(context, profile, product) + .withPutCode("123456") + .build(); + + addMetadata(product, "dc", "contributor", "author", "Test User", profile.getID().toString()); + + context.restoreAuthSystemState(); + context.commit(); + + List orcidQueueRecords = orcidQueueService.findAll(context); + assertThat(orcidQueueRecords, hasSize(1)); + assertThat(orcidQueueRecords.get(0), matches(profile, product, "Product", "123456", UPDATE)); + } + + @Test + public void testOrcidQueueRecordCreationToUpdatePatent() throws Exception { + + context.turnOffAuthorisationSystem(); + + Item profile = ItemBuilder.createItem(context, profileCollection) + .withTitle("Test User") + .withOrcidIdentifier("0000-1111-2222-3333") + .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", eperson) + .withOrcidSynchronizationPatentsPreference(ALL) + .build(); + + Collection patentCollection = createCollection("Patents", "Patent"); + + Item patent = ItemBuilder.createItem(context, patentCollection) + .withTitle("Test patent") + .build(); + + createOrcidHistory(context, profile, patent) + .withPutCode("123456") + .build(); + + addMetadata(patent, "dc", "contributor", "author", "Test User", profile.getID().toString()); + + context.restoreAuthSystemState(); + context.commit(); + + List orcidQueueRecords = orcidQueueService.findAll(context); + assertThat(orcidQueueRecords, hasSize(1)); + assertThat(orcidQueueRecords.get(0), matches(profile, patent, "Patent", "123456", UPDATE)); + } + @Test public void testNoOrcidQueueRecordCreationOccursIfFundingSynchronizationIsDisabled() throws Exception { @@ -648,6 +782,66 @@ public void testNoOrcidQueueRecordCreationOccursIfFundingSynchronizationIsDisabl assertThat(orcidQueueService.findAll(context), empty()); } + @Test + public void testNoOrcidQueueRecordCreationOccursIfProductSynchronizationIsDisabled() throws Exception { + + context.turnOffAuthorisationSystem(); + + Item profile = ItemBuilder.createItem(context, profileCollection) + .withTitle("Test User") + .withOrcidIdentifier("0000-1111-2222-3333") + .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", eperson) + .build(); + + Collection productCollection = createCollection("Products", "Product"); + + Item product = ItemBuilder.createItem(context, productCollection) + .withTitle("Test product") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + context.restoreAuthSystemState(); + context.commit(); + + assertThat(orcidQueueService.findAll(context), empty()); + + addMetadata(profile, "dspace", "orcid", "sync-products", DISABLED.name(), null); + addMetadata(product, "dc", "description", "abstract", "Product Poduct Pduct Puct Pct Pt P", null); + context.commit(); + + assertThat(orcidQueueService.findAll(context), empty()); + } + + @Test + public void testNoOrcidQueueRecordCreationOccursIfPatentSynchronizationIsDisabled() throws Exception { + + context.turnOffAuthorisationSystem(); + + Item profile = ItemBuilder.createItem(context, profileCollection) + .withTitle("Test User") + .withOrcidIdentifier("0000-1111-2222-3333") + .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", eperson) + .build(); + + Collection patentCollection = createCollection("Patents", "Patent"); + + Item patent = ItemBuilder.createItem(context, patentCollection) + .withTitle("Test patent") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + context.restoreAuthSystemState(); + context.commit(); + + assertThat(orcidQueueService.findAll(context), empty()); + + addMetadata(profile, "dspace", "orcid", "sync-patents", DISABLED.name(), null); + addMetadata(patent, "dc", "description", "abstract", "Patent Ptent Pent Pnt Pt P", null); + context.commit(); + + assertThat(orcidQueueService.findAll(context), empty()); + } + @Test public void testNoOrcidQueueRecordCreationOccursIfProfileHasNotOrcidIdentifier() throws Exception { @@ -657,6 +851,8 @@ public void testNoOrcidQueueRecordCreationOccursIfProfileHasNotOrcidIdentifier() .withTitle("Test User") .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", eperson) .withOrcidSynchronizationFundingsPreference(ALL) + .withOrcidSynchronizationProductsPreference(ALL) + .withOrcidSynchronizationPatentsPreference(ALL) .build(); Collection fundingCollection = createCollection("Fundings", "Funding"); @@ -666,6 +862,20 @@ public void testNoOrcidQueueRecordCreationOccursIfProfileHasNotOrcidIdentifier() .withFundingInvestigator("Test User", profile.getID().toString()) .build(); + Collection productCollection = createCollection("Products", "Product"); + + ItemBuilder.createItem(context, productCollection) + .withTitle("Test product") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + Collection patentsCollection = createCollection("Patents", "Patent"); + + ItemBuilder.createItem(context, patentsCollection) + .withTitle("Test patent") + .withAuthor("Test User", profile.getID().toString()) + .build(); + context.restoreAuthSystemState(); context.commit(); @@ -834,6 +1044,8 @@ public void testWithMetadataFieldToIgnore() throws Exception { .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", eperson) .withOrcidSynchronizationFundingsPreference(ALL) .withOrcidSynchronizationPublicationsPreference(ALL) + .withOrcidSynchronizationProductsPreference(ALL) + .withOrcidSynchronizationPatentsPreference(ALL) .build(); Collection publicationCollection = createCollection("Publications", "Publication"); @@ -866,6 +1078,20 @@ public void testWithMetadataFieldToIgnore() throws Exception { .withFundingCoInvestigator("Test User", profile.getID().toString()) .build(); + Collection productCollection = createCollection("Products", "Product"); + + Item firstProduct = ItemBuilder.createItem(context, productCollection) + .withTitle("Test product") + .withAuthor("Test User", profile.getID().toString()) + .build(); + + Collection patentCollection = createCollection("Patents", "Patent"); + + Item firstPatent = ItemBuilder.createItem(context, patentCollection) + .withTitle("Test patent") + .withAuthor("Test User", profile.getID().toString()) + .build(); + context.restoreAuthSystemState(); List records = orcidQueueService.findAll(context); @@ -873,7 +1099,6 @@ public void testWithMetadataFieldToIgnore() throws Exception { assertThat(records, hasItem(matches(profile, firstPublication, "Publication", null, INSERT))); assertThat(records, hasItem(matches(profile, secondPublication, "Publication", null, INSERT))); assertThat(records, hasItem(matches(profile, firstFunding, "Funding", null, INSERT))); - } @Test diff --git a/dspace-api/src/test/java/org/dspace/orcid/service/OrcidEntityFactoryServiceIT.java b/dspace-api/src/test/java/org/dspace/orcid/service/OrcidEntityFactoryServiceIT.java index d582b7c6058b..f3ce446a8cab 100644 --- a/dspace-api/src/test/java/org/dspace/orcid/service/OrcidEntityFactoryServiceIT.java +++ b/dspace-api/src/test/java/org/dspace/orcid/service/OrcidEntityFactoryServiceIT.java @@ -73,8 +73,12 @@ public class OrcidEntityFactoryServiceIT extends AbstractIntegrationTestWithData private Collection orgUnits; + private Collection patents; + private Collection publications; + private Collection products; + private Collection fundings; @Before @@ -98,11 +102,26 @@ public void setup() { .withEntityType("OrgUnit") .build(); + patents = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .withEntityType("Patent") + .build(); + publications = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection") .withEntityType("Publication") .build(); + products = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .withEntityType("Product") + .build(); + + products = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .withEntityType("Product") + .build(); + fundings = CollectionBuilder.createCollection(context, parentCommunity) .withName("Collection") .withEntityType("Funding") @@ -174,6 +193,126 @@ public void testWorkCreation() { } + @Test + public void testProductWorkCreation() { + + context.turnOffAuthorisationSystem(); + + Item author = ItemBuilder.createItem(context, persons) + .withTitle("Jesse Pinkman") + .withOrcidIdentifier("0000-1111-2222-3333") + .withPersonEmail("test@test.it") + .build(); + + Item product = ItemBuilder.createItem(context, products) + .withTitle("Test dataset") + .withAuthor("Walter White") + .withAuthor("Jesse Pinkman", author.getID().toString()) + .withEditor("Editor") + .withIssueDate("2021-04-30") + .withDescriptionAbstract("Product description") + .withLanguage("en_US") + .withType("http://purl.org/coar/resource_type/c_ddb1") + .withIsPartOf("Collection of Products") + .withDoiIdentifier("doi-id") + .withScopusIdentifier("scopus-id") + .build(); + + context.restoreAuthSystemState(); + + Activity activity = entityFactoryService.createOrcidObject(context, product); + assertThat(activity, instanceOf(Work.class)); + + Work work = (Work) activity; + assertThat(work.getJournalTitle(), notNullValue()); + assertThat(work.getJournalTitle().getContent(), is("Collection of Products")); + assertThat(work.getLanguageCode(), is("en")); + assertThat(work.getPublicationDate(), matches(date("2021", "04", "30"))); + assertThat(work.getShortDescription(), is("Product description")); + assertThat(work.getPutCode(), nullValue()); + // assertThat(work.getWorkCitation(), notNullValue()); + // assertThat(work.getWorkCitation().getCitation(), containsString("Test product")); + assertThat(work.getWorkType(), is(WorkType.DATA_SET)); + assertThat(work.getWorkTitle(), notNullValue()); + assertThat(work.getWorkTitle().getTitle(), notNullValue()); + assertThat(work.getWorkTitle().getTitle().getContent(), is("Test dataset")); + assertThat(work.getWorkContributors(), notNullValue()); + assertThat(work.getUrl(), matches(urlEndsWith(product.getHandle()))); + + List contributors = work.getWorkContributors().getContributor(); + assertThat(contributors, hasSize(2)); + assertThat(contributors, has(contributor("Walter White", AUTHOR, FIRST))); + // assertThat(contributors, has(contributor("Editor", EDITOR, FIRST))); + assertThat(contributors, has(contributor("Jesse Pinkman", AUTHOR, ADDITIONAL, + "0000-1111-2222-3333", "test@test.it"))); + + assertThat(work.getExternalIdentifiers(), notNullValue()); + + List externalIds = work.getExternalIdentifiers().getExternalIdentifier(); + assertThat(externalIds, hasSize(3)); + assertThat(externalIds, has(selfExternalId("doi", "doi-id"))); + assertThat(externalIds, has(selfExternalId("eid", "scopus-id"))); + assertThat(externalIds, has(selfExternalId("handle", product.getHandle()))); + + } + + @Test + public void testPatentWorkCreation() { + + context.turnOffAuthorisationSystem(); + + Item author = ItemBuilder.createItem(context, persons) + .withTitle("Jesse Pinkman") + .withOrcidIdentifier("0000-1111-2222-3333") + .withPersonEmail("test@test.it") + .build(); + + Item patent = ItemBuilder.createItem(context, patents) + .withTitle("Test patent") + .withAuthor("Jesse Pinkman", author.getID().toString()) + .withIssueDate("2021-04-30") + .withDescriptionAbstract("Patent description") + .withPublisher("Patent registration office") + .withLanguage("en_US") + .withPatentNo("2021.01.0111") + .withType("http://purl.org/coar/resource_type/c_15cd") + .build(); + + context.restoreAuthSystemState(); + + Activity activity = entityFactoryService.createOrcidObject(context, patent); + assertThat(activity, instanceOf(Work.class)); + + Work work = (Work) activity; + assertThat(work.getJournalTitle(), notNullValue()); + assertThat(work.getJournalTitle().getContent(), is("Patent registration office")); + assertThat(work.getLanguageCode(), is("en")); + assertThat(work.getPublicationDate(), matches(date("2021", "04", "30"))); + assertThat(work.getShortDescription(), is("Patent description")); + assertThat(work.getPutCode(), nullValue()); + // assertThat(work.getWorkCitation(), notNullValue()); + // assertThat(work.getWorkCitation().getCitation(), containsString("Test patent")); + assertThat(work.getWorkType(), is(WorkType.PATENT)); + assertThat(work.getWorkTitle(), notNullValue()); + assertThat(work.getWorkTitle().getTitle(), notNullValue()); + assertThat(work.getWorkTitle().getTitle().getContent(), is("Test patent")); + assertThat(work.getWorkContributors(), notNullValue()); + assertThat(work.getUrl(), matches(urlEndsWith(patent.getHandle()))); + + List contributors = work.getWorkContributors().getContributor(); + assertThat(contributors, hasSize(1)); + assertThat(contributors, has(contributor("Jesse Pinkman", AUTHOR, FIRST, + "0000-1111-2222-3333", "test@test.it"))); + + assertThat(work.getExternalIdentifiers(), notNullValue()); + + List externalIds = work.getExternalIdentifiers().getExternalIdentifier(); + assertThat(externalIds, hasSize(2)); + assertThat(externalIds, has(selfExternalId("handle", patent.getHandle()))); + assertThat(externalIds, has(selfExternalId("pat", "2021.01.0111"))); + + } + @Test public void testWorkWithFundingCreation() { context.turnOffAuthorisationSystem(); @@ -200,6 +339,59 @@ public void testWorkWithFundingCreation() { assertThat(externalIds, has(fundedByExternalId("grant_number", "123456"))); } + @Test + public void testProductWorkWithFundingCreation() { + context.turnOffAuthorisationSystem(); + + Item product = ItemBuilder.createItem(context, products) + .withTitle("Test dataset") + .withAuthor("Walter White") + .withIssueDate("2021-04-30") + .withType("http://purl.org/coar/resource_type/H6QP-SC1X") + .withRelationFunding("Test funding") + .withRelationGrantno("123456") + .build(); + + context.restoreAuthSystemState(); + + Activity activity = entityFactoryService.createOrcidObject(context, product); + assertThat(activity, instanceOf(Work.class)); + + Work work = (Work) activity; + + List externalIds = work.getExternalIdentifiers().getExternalIdentifier(); + assertThat(externalIds, hasSize(2)); + assertThat(externalIds, has(selfExternalId("handle", product.getHandle()))); + assertThat(externalIds, has(fundedByExternalId("grant_number", "123456"))); + } + + @Test + public void testPatentsWorkWithFundingCreation() { + context.turnOffAuthorisationSystem(); + + Item patent = ItemBuilder.createItem(context, patents) + .withTitle("Test patent") + .withAuthor("Walter White") + .withIssueDate("2021-04-30") + .withType("http://purl.org/coar/resource_type/c_15cd") + .withRelationFunding("Test funding") + .withRelationGrantno("123456") + .build(); + + context.restoreAuthSystemState(); + + Activity activity = entityFactoryService.createOrcidObject(context, patent); + assertThat(activity, instanceOf(Work.class)); + + Work work = (Work) activity; + + List externalIds = work.getExternalIdentifiers().getExternalIdentifier(); + assertThat(externalIds, hasSize(2)); + assertThat(externalIds, has(selfExternalId("handle", patent.getHandle()))); + assertThat(externalIds, has(fundedByExternalId("grant_number", "123456"))); + } + + @Test public void testWorkWithFundingWithoutGrantNumberCreation() { context.turnOffAuthorisationSystem(); @@ -224,6 +416,54 @@ public void testWorkWithFundingWithoutGrantNumberCreation() { assertThat(externalIds, has(selfExternalId("handle", publication.getHandle()))); } + @Test + public void testProductWorkWithFundingWithoutGrantNumberCreation() { + context.turnOffAuthorisationSystem(); + + Item product = ItemBuilder.createItem(context, products) + .withTitle("Test product") + .withAuthor("Walter White") + .withIssueDate("2021-04-30") + .withType("http://purl.org/coar/resource_type/c_e9a0") + .withRelationFunding("Test funding") + .build(); + + context.restoreAuthSystemState(); + + Activity activity = entityFactoryService.createOrcidObject(context, product); + assertThat(activity, instanceOf(Work.class)); + + Work work = (Work) activity; + + List externalIds = work.getExternalIdentifiers().getExternalIdentifier(); + assertThat(externalIds, hasSize(1)); + assertThat(externalIds, has(selfExternalId("handle", product.getHandle()))); + } + + @Test + public void testPatentWorkWithFundingWithoutGrantNumberCreation() { + context.turnOffAuthorisationSystem(); + + Item patent = ItemBuilder.createItem(context, patents) + .withTitle("Test patent") + .withAuthor("Walter White") + .withIssueDate("2021-04-30") + .withType("http://purl.org/coar/resource_type/c_15cd") + .withRelationFunding("Test funding") + .build(); + + context.restoreAuthSystemState(); + + Activity activity = entityFactoryService.createOrcidObject(context, patent); + assertThat(activity, instanceOf(Work.class)); + + Work work = (Work) activity; + + List externalIds = work.getExternalIdentifiers().getExternalIdentifier(); + assertThat(externalIds, hasSize(1)); + assertThat(externalIds, has(selfExternalId("handle", patent.getHandle()))); + } + @Test public void testWorkWithFundingWithGrantNumberPlaceholderCreation() { context.turnOffAuthorisationSystem(); @@ -249,12 +489,63 @@ public void testWorkWithFundingWithGrantNumberPlaceholderCreation() { assertThat(externalIds, has(selfExternalId("handle", publication.getHandle()))); } + @Test + public void testProductWorkWithFundingWithGrantNumberPlaceholderCreation() { + context.turnOffAuthorisationSystem(); + + Item product = ItemBuilder.createItem(context, products) + .withTitle("Test dataset") + .withAuthor("Walter White") + .withIssueDate("2021-04-30") + .withType("http://purl.org/coar/resource_type/c_7ad9") + .withRelationFunding("Test funding") + .withRelationGrantno(CrisConstants.PLACEHOLDER_PARENT_METADATA_VALUE) + .build(); + + context.restoreAuthSystemState(); + + Activity activity = entityFactoryService.createOrcidObject(context, product); + assertThat(activity, instanceOf(Work.class)); + + Work work = (Work) activity; + + List externalIds = work.getExternalIdentifiers().getExternalIdentifier(); + assertThat(externalIds, hasSize(1)); + assertThat(externalIds, has(selfExternalId("handle", product.getHandle()))); + } + + @Test + public void testPatentWorkWithFundingWithGrantNumberPlaceholderCreation() { + context.turnOffAuthorisationSystem(); + + Item patent = ItemBuilder.createItem(context, patents) + .withTitle("Test patent") + .withAuthor("Walter White") + .withIssueDate("2021-04-30") + .withType("http://purl.org/coar/resource_type/c_15cd") + .withRelationFunding("Test funding") + .withRelationGrantno(CrisConstants.PLACEHOLDER_PARENT_METADATA_VALUE) + .build(); + + context.restoreAuthSystemState(); + + Activity activity = entityFactoryService.createOrcidObject(context, patent); + assertThat(activity, instanceOf(Work.class)); + + Work work = (Work) activity; + + List externalIds = work.getExternalIdentifiers().getExternalIdentifier(); + assertThat(externalIds, hasSize(1)); + assertThat(externalIds, has(selfExternalId("handle", patent.getHandle()))); + } + + @Test public void testWorkWithFundingEntityWithoutGrantNumberCreation() { context.turnOffAuthorisationSystem(); - Item funding = ItemBuilder.createItem(context, publications) + Item funding = ItemBuilder.createItem(context, fundings) .withTitle("Test funding") .build(); @@ -280,12 +571,74 @@ public void testWorkWithFundingEntityWithoutGrantNumberCreation() { assertThat(externalIds, has(fundedByExternalId("grant_number", "123456"))); } + @Test + public void testProductWorkWithFundingEntityWithoutGrantNumberCreation() { + + context.turnOffAuthorisationSystem(); + + Item funding = ItemBuilder.createItem(context, fundings) + .withTitle("Test funding") + .build(); + + Item product = ItemBuilder.createItem(context, products) + .withTitle("Test product") + .withAuthor("Walter White") + .withIssueDate("2021-04-30") + .withType("http://purl.org/coar/resource_type/c_7ad9") + .withRelationFunding("Test funding", funding.getID().toString()) + .withRelationGrantno("123456") + .build(); + + context.restoreAuthSystemState(); + + Activity activity = entityFactoryService.createOrcidObject(context, product); + assertThat(activity, instanceOf(Work.class)); + + Work work = (Work) activity; + + List externalIds = work.getExternalIdentifiers().getExternalIdentifier(); + assertThat(externalIds, hasSize(2)); + assertThat(externalIds, has(selfExternalId("handle", product.getHandle()))); + assertThat(externalIds, has(fundedByExternalId("grant_number", "123456"))); + } + + @Test + public void testPatentWorkWithFundingEntityWithoutGrantNumberCreation() { + + context.turnOffAuthorisationSystem(); + + Item funding = ItemBuilder.createItem(context, fundings) + .withTitle("Test funding") + .build(); + + Item patent = ItemBuilder.createItem(context, patents) + .withTitle("Test patent") + .withAuthor("Walter White") + .withIssueDate("2021-04-30") + .withType("http://purl.org/coar/resource_type/c_15cd") + .withRelationFunding("Test funding", funding.getID().toString()) + .withRelationGrantno("123456") + .build(); + + context.restoreAuthSystemState(); + + Activity activity = entityFactoryService.createOrcidObject(context, patent); + assertThat(activity, instanceOf(Work.class)); + + Work work = (Work) activity; + + List externalIds = work.getExternalIdentifiers().getExternalIdentifier(); + assertThat(externalIds, hasSize(2)); + assertThat(externalIds, has(selfExternalId("handle", patent.getHandle()))); + assertThat(externalIds, has(fundedByExternalId("grant_number", "123456"))); + } + @Test public void testWorkWithFundingEntityWithGrantNumberCreation() { context.turnOffAuthorisationSystem(); - Item funding = ItemBuilder.createItem(context, publications) + Item funding = ItemBuilder.createItem(context, fundings) .withHandle("123456789/0001") .withTitle("Test funding") .withFundingIdentifier("987654") @@ -314,12 +667,81 @@ public void testWorkWithFundingEntityWithGrantNumberCreation() { "http://localhost:4000/handle/123456789/0001"))); } + @Test + public void testProductWorkWithFundingEntityWithGrantNumberCreation() { + + context.turnOffAuthorisationSystem(); + + Item funding = ItemBuilder.createItem(context, fundings) + .withHandle("123456789/0001") + .withTitle("Test funding") + .withFundingIdentifier("987654") + .build(); + + Item product = ItemBuilder.createItem(context, products) + .withTitle("Test product") + .withAuthor("Walter White") + .withIssueDate("2021-04-30") + .withType("http://purl.org/coar/resource_type/c_18cc") + .withRelationFunding("Test funding", funding.getID().toString()) + .withRelationGrantno("123456") + .build(); + + context.restoreAuthSystemState(); + + Activity activity = entityFactoryService.createOrcidObject(context, product); + assertThat(activity, instanceOf(Work.class)); + + Work work = (Work) activity; + + List externalIds = work.getExternalIdentifiers().getExternalIdentifier(); + assertThat(externalIds, hasSize(2)); + assertThat(externalIds, has(selfExternalId("handle", product.getHandle()))); + assertThat(externalIds, has(fundedByExternalId("grant_number", "987654", + "http://localhost:4000/handle/123456789/0001"))); + } + + @Test + public void testPatentWorkWithFundingEntityWithGrantNumberCreation() { + + context.turnOffAuthorisationSystem(); + + Item funding = ItemBuilder.createItem(context, fundings) + .withHandle("123456789/0001") + .withTitle("Test funding") + .withFundingIdentifier("987654") + .build(); + + Item patent = ItemBuilder.createItem(context, patents) + .withTitle("Test patent") + .withAuthor("Walter White") + .withIssueDate("2021-04-30") + .withType("http://purl.org/coar/resource_type/c_15cd") + .withRelationFunding("Test funding", funding.getID().toString()) + .withRelationGrantno("123456") + .build(); + + context.restoreAuthSystemState(); + + Activity activity = entityFactoryService.createOrcidObject(context, patent); + assertThat(activity, instanceOf(Work.class)); + + Work work = (Work) activity; + + List externalIds = work.getExternalIdentifiers().getExternalIdentifier(); + assertThat(externalIds, hasSize(2)); + assertThat(externalIds, has(selfExternalId("handle", patent.getHandle()))); + assertThat(externalIds, has(fundedByExternalId("grant_number", "987654", + "http://localhost:4000/handle/123456789/0001"))); + } + + @Test public void testWorkWithFundingEntityWithGrantNumberAndUrlCreation() { context.turnOffAuthorisationSystem(); - Item funding = ItemBuilder.createItem(context, publications) + Item funding = ItemBuilder.createItem(context, fundings) .withHandle("123456789/0001") .withTitle("Test funding") .withFundingIdentifier("987654") @@ -348,6 +770,74 @@ public void testWorkWithFundingEntityWithGrantNumberAndUrlCreation() { assertThat(externalIds, has(fundedByExternalId("grant_number", "987654", "http://test-funding"))); } + @Test + public void testProductWorkWithFundingEntityWithGrantNumberAndUrlCreation() { + + context.turnOffAuthorisationSystem(); + + Item funding = ItemBuilder.createItem(context, fundings) + .withHandle("123456789/0001") + .withTitle("Test funding") + .withFundingIdentifier("987654") + .withFundingAwardUrl("http://test-funding") + .build(); + + Item product = ItemBuilder.createItem(context, products) + .withTitle("Test product") + .withAuthor("Walter White") + .withIssueDate("2021-04-30") + .withType("http://purl.org/coar/resource_type/c_18cc") + .withRelationFunding("Test funding", funding.getID().toString()) + .withRelationGrantno("123456") + .build(); + + context.restoreAuthSystemState(); + + Activity activity = entityFactoryService.createOrcidObject(context, product); + assertThat(activity, instanceOf(Work.class)); + + Work work = (Work) activity; + + List externalIds = work.getExternalIdentifiers().getExternalIdentifier(); + assertThat(externalIds, hasSize(2)); + assertThat(externalIds, has(selfExternalId("handle", product.getHandle()))); + assertThat(externalIds, has(fundedByExternalId("grant_number", "987654", "http://test-funding"))); + } + + @Test + public void testPatentWorkWithFundingEntityWithGrantNumberAndUrlCreation() { + + context.turnOffAuthorisationSystem(); + + Item funding = ItemBuilder.createItem(context, fundings) + .withHandle("123456789/0001") + .withTitle("Test funding") + .withFundingIdentifier("987654") + .withFundingAwardUrl("http://test-funding") + .build(); + + Item patent = ItemBuilder.createItem(context, patents) + .withTitle("Test patent") + .withAuthor("Walter White") + .withIssueDate("2021-04-30") + .withType("http://purl.org/coar/resource_type/c_15cd") + .withRelationFunding("Test funding", funding.getID().toString()) + .withRelationGrantno("123456") + .build(); + + context.restoreAuthSystemState(); + + Activity activity = entityFactoryService.createOrcidObject(context, patent); + assertThat(activity, instanceOf(Work.class)); + + Work work = (Work) activity; + + List externalIds = work.getExternalIdentifiers().getExternalIdentifier(); + assertThat(externalIds, hasSize(2)); + assertThat(externalIds, has(selfExternalId("handle", patent.getHandle()))); + assertThat(externalIds, has(fundedByExternalId("grant_number", "987654", "http://test-funding"))); + } + @Test public void testEmptyWorkWithUnknownTypeCreation() { @@ -380,6 +870,71 @@ public void testEmptyWorkWithUnknownTypeCreation() { assertThat(externalIds, has(selfExternalId("handle", publication.getHandle()))); } + @Test + public void testEmptyProductWorkWithUnknownTypeCreation() { + + context.turnOffAuthorisationSystem(); + + Item product = ItemBuilder.createItem(context, products) + .withType("http://purl.org/coar/resource_type/") + .build(); + + context.restoreAuthSystemState(); + + Activity activity = entityFactoryService.createOrcidObject(context, product); + assertThat(activity, instanceOf(Work.class)); + + Work work = (Work) activity; + assertThat(work.getJournalTitle(), nullValue()); + assertThat(work.getLanguageCode(), nullValue()); + assertThat(work.getPublicationDate(), nullValue()); + assertThat(work.getShortDescription(), nullValue()); + assertThat(work.getPutCode(), nullValue()); + // assertThat(work.getWorkCitation(), notNullValue()); + assertThat(work.getWorkType(), is(WorkType.DATA_SET)); + assertThat(work.getWorkTitle(), nullValue()); + assertThat(work.getWorkContributors(), notNullValue()); + assertThat(work.getWorkContributors().getContributor(), empty()); + assertThat(work.getExternalIdentifiers(), notNullValue()); + + List externalIds = work.getExternalIdentifiers().getExternalIdentifier(); + assertThat(externalIds, hasSize(1)); + assertThat(externalIds, has(selfExternalId("handle", product.getHandle()))); + } + + @Test + public void testEmptyPatentWorkWithUnknownTypeCreation() { + + context.turnOffAuthorisationSystem(); + + Item patent = ItemBuilder.createItem(context, patents) + .withType("http://purl.org/coar/resource_type/") + .build(); + + context.restoreAuthSystemState(); + + Activity activity = entityFactoryService.createOrcidObject(context, patent); + assertThat(activity, instanceOf(Work.class)); + + Work work = (Work) activity; + assertThat(work.getJournalTitle(), nullValue()); + assertThat(work.getLanguageCode(), nullValue()); + assertThat(work.getPublicationDate(), nullValue()); + assertThat(work.getShortDescription(), nullValue()); + assertThat(work.getPutCode(), nullValue()); + // assertThat(work.getWorkCitation(), notNullValue()); + assertThat(work.getWorkType(), is(WorkType.PATENT)); + assertThat(work.getWorkTitle(), nullValue()); + assertThat(work.getWorkContributors(), notNullValue()); + assertThat(work.getWorkContributors().getContributor(), empty()); + assertThat(work.getExternalIdentifiers(), notNullValue()); + + List externalIds = work.getExternalIdentifiers().getExternalIdentifier(); + assertThat(externalIds, hasSize(1)); + assertThat(externalIds, has(selfExternalId("handle", patent.getHandle()))); + } + + @Test public void testFundingCreation() { context.turnOffAuthorisationSystem(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResearcherProfileConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResearcherProfileConverter.java index 54dffa57881e..841a3b8eebd7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResearcherProfileConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResearcherProfileConverter.java @@ -8,6 +8,8 @@ package org.dspace.app.rest.converter; import static org.dspace.orcid.model.OrcidEntityType.FUNDING; +import static org.dspace.orcid.model.OrcidEntityType.PATENT; +import static org.dspace.orcid.model.OrcidEntityType.PRODUCT; import static org.dspace.orcid.model.OrcidEntityType.PUBLICATION; import java.util.List; @@ -60,6 +62,8 @@ public ResearcherProfileRest convert(ResearcherProfile profile, Projection proje orcidSynchronization.setProfilePreferences(getProfilePreferences(item)); orcidSynchronization.setFundingsPreference(getFundingsPreference(item)); orcidSynchronization.setPublicationsPreference(getPublicationsPreference(item)); + orcidSynchronization.setProductsPreference(getProductsPreference(item)); + orcidSynchronization.setPatentsPreference(getPatentsPreference(item)); researcherProfileRest.setOrcidSynchronization(orcidSynchronization); } @@ -72,6 +76,19 @@ private String getPublicationsPreference(Item item) { .orElse(OrcidEntitySyncPreference.DISABLED.name()); } + private String getProductsPreference(Item item) { + return orcidSynchronizationService.getEntityPreference(item, PRODUCT) + .map(OrcidEntitySyncPreference::name) + .orElse(OrcidEntitySyncPreference.DISABLED.name()); + } + + private String getPatentsPreference(Item item) { + return orcidSynchronizationService.getEntityPreference(item, PATENT) + .map(OrcidEntitySyncPreference::name) + .orElse(OrcidEntitySyncPreference.DISABLED.name()); + } + + private String getFundingsPreference(Item item) { return orcidSynchronizationService.getEntityPreference(item, FUNDING) .map(OrcidEntitySyncPreference::name) @@ -95,4 +112,4 @@ public Class getModelClass() { return ResearcherProfile.class; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/enhancer/RelatedItemEnhancerUpdatePoller.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/enhancer/RelatedItemEnhancerUpdatePoller.java new file mode 100644 index 000000000000..b4af863e6542 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/enhancer/RelatedItemEnhancerUpdatePoller.java @@ -0,0 +1,68 @@ +/** + * 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.enhancer; + +import java.sql.SQLException; +import java.util.UUID; + +import org.dspace.content.Item; +import org.dspace.content.enhancer.service.ItemEnhancerService; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Component +@ConditionalOnProperty("related-item-enhancer-poller.enabled") +public class RelatedItemEnhancerUpdatePoller { + private static final Logger log = LoggerFactory.getLogger(RelatedItemEnhancerUpdatePoller.class); + @Autowired + private ItemEnhancerService itemEnhancerService; + + @Autowired + private ItemService itemService; + + @Scheduled(fixedDelayString = "${related-item-enhancer-poller.delay}") + public void pollItemToUpdateAndProcess() { + try { + log.debug("item enhancer poller executed"); + Context context = new Context(); + context.turnOffAuthorisationSystem(); + UUID extractedUuid; + while ((extractedUuid = itemEnhancerService.pollItemToUpdate(context)) != null) { + log.debug("item enhancer poller processing {}", extractedUuid); + Item item = itemService.find(context, extractedUuid); + if (item != null) { + itemEnhancerService.enhance(context, item, true); + } + log.debug("item enhancer poller committing"); + context.commit(); + } + context.restoreAuthSystemState(); + context.complete(); + } catch (SQLException e) { + log.error("Error polling items to update for metadata enrichment", e); + } + } + + public void setItemEnhancerService(ItemEnhancerService itemEnhancerService) { + this.itemEnhancerService = itemEnhancerService; + } + + public ItemEnhancerService getItemEnhancerService() { + return itemEnhancerService; + } + + public void setItemService(ItemService itemService) { + this.itemService = itemService; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java index 4224cfeeb924..6133ae36e266 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ResearcherProfileRest.java @@ -91,8 +91,12 @@ public static class OrcidSynchronizationRest { private String publicationsPreference; + private String productsPreference; + private String fundingsPreference; + private String patentsPreference; + private List profilePreferences; public String getMode() { @@ -119,6 +123,22 @@ public void setPublicationsPreference(String publicationsPreference) { this.publicationsPreference = publicationsPreference; } + public String getProductsPreference() { + return productsPreference; + } + + public void setProductsPreference(String productsPreference) { + this.productsPreference = productsPreference; + } + + public String getPatentsPreference() { + return patentsPreference; + } + + public void setPatentsPreference(String patentsPreference) { + this.patentsPreference = patentsPreference; + } + public String getFundingsPreference() { return fundingsPreference; } @@ -129,4 +149,4 @@ public void setFundingsPreference(String fundingsPreference) { } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ResearcherProfileReplaceOrcidSyncPreferencesOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ResearcherProfileReplaceOrcidSyncPreferencesOperation.java index 5084931382a5..e6e79684e84f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ResearcherProfileReplaceOrcidSyncPreferencesOperation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/operation/ResearcherProfileReplaceOrcidSyncPreferencesOperation.java @@ -8,6 +8,8 @@ package org.dspace.app.rest.repository.patch.operation; import static org.dspace.orcid.model.OrcidEntityType.FUNDING; +import static org.dspace.orcid.model.OrcidEntityType.PATENT; +import static org.dspace.orcid.model.OrcidEntityType.PRODUCT; import static org.dspace.orcid.model.OrcidEntityType.PUBLICATION; import java.sql.SQLException; @@ -54,6 +56,10 @@ public class ResearcherProfileReplaceOrcidSyncPreferencesOperation extends Patch private static final String PUBLICATIONS_PREFERENCES = "/publications"; + private static final String PRODUCTS_PREFERENCES = "/products"; + + private static final String PATENTS_PREFERENCES = "/patents"; + private static final String FUNDINGS_PREFERENCES = "/fundings"; private static final String PROFILE_PREFERENCES = "/profile"; @@ -121,6 +127,12 @@ private boolean updatePreferences(Context context, String path, String value, It case PUBLICATIONS_PREFERENCES: OrcidEntitySyncPreference preference = parsePreference(value); return synchronizationService.setEntityPreference(context, profileItem, PUBLICATION, preference); + case PRODUCTS_PREFERENCES: + OrcidEntitySyncPreference productPreference = parsePreference(value); + return synchronizationService.setEntityPreference(context, profileItem, PRODUCT, productPreference); + case PATENTS_PREFERENCES: + OrcidEntitySyncPreference patentsPreference = parsePreference(value); + return synchronizationService.setEntityPreference(context, profileItem, PATENT, patentsPreference); case FUNDINGS_PREFERENCES: OrcidEntitySyncPreference fundingPreference = parsePreference(value); return synchronizationService.setEntityPreference(context, profileItem, FUNDING, fundingPreference); @@ -137,9 +149,19 @@ private boolean updatePreferences(Context context, String path, String value, It private void reloadOrcidQueue(Context context, String path, String value, Item profileItem) throws SQLException, AuthorizeException { - if (path.equals(PUBLICATIONS_PREFERENCES) || path.equals(FUNDINGS_PREFERENCES)) { + if (path.equals(PUBLICATIONS_PREFERENCES) || path.equals(FUNDINGS_PREFERENCES) + || path.equals(PRODUCTS_PREFERENCES) || path.equals(PATENTS_PREFERENCES)) { OrcidEntitySyncPreference preference = parsePreference(value); - OrcidEntityType entityType = path.equals(PUBLICATIONS_PREFERENCES) ? PUBLICATION : FUNDING; + OrcidEntityType entityType = FUNDING; + if (path.equals(PUBLICATIONS_PREFERENCES)) { + entityType = PUBLICATION; + } + if (path.equals(PRODUCTS_PREFERENCES)) { + entityType = PRODUCT; + } + if (path.equals(PATENTS_PREFERENCES)) { + entityType = PATENT; + } orcidQueueService.recalculateOrcidQueue(context, profileItem, entityType, preference); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java index 1ad0765a0f8c..adc9d3c51c7c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java @@ -44,6 +44,8 @@ import org.dspace.scripts.factory.ScriptServiceFactory; import org.dspace.scripts.handler.DSpaceRunnableHandler; import org.dspace.scripts.service.ProcessService; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.utils.DSpace; import org.springframework.core.task.TaskExecutor; @@ -54,6 +56,7 @@ public class RestDSpaceRunnableHandler implements DSpaceRunnableHandler { private static final Logger log = org.apache.logging.log4j.LogManager .getLogger(RestDSpaceRunnableHandler.class); + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); private BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); private ProcessService processService = ScriptServiceFactory.getInstance().getProcessService(); private EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); @@ -298,8 +301,10 @@ public Process getProcess(Context context) { * @param script The script to be ran */ public void schedule(DSpaceRunnable script) { + String taskExecutorBeanName = configurationService.getProperty("dspace.task.executor", + "dspaceRunnableThreadExecutor"); TaskExecutor taskExecutor = new DSpace().getServiceManager() - .getServiceByName("dspaceRunnableThreadExecutor", TaskExecutor.class); + .getServiceByName(taskExecutorBeanName, TaskExecutor.class); Context context = new Context(); try { Process process = processService.find(context, processId); @@ -360,4 +365,17 @@ public List getSpecialGroups() { public Locale getLocale() { return this.locale; } + + public Integer getProcessId() { + return processId; + } + + public String getScriptName() { + return scriptName; + } + + public UUID getePersonId() { + return ePersonId; + } + } 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 1c9f4e821df2..867dd0b8d9fc 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 @@ -70,7 +70,36 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/batch/ImportBatchIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/batch/ImportBatchIT.java index 95306b49b930..0b34fc4025e8 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/batch/ImportBatchIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/batch/ImportBatchIT.java @@ -1157,10 +1157,10 @@ public void testDSpaceEntityTypeIsKeeped() throws SQLException { ImpRecordService.INSERT_OR_UPDATE_OPERATION, admin, publicationCollection); createImpMetadatavalue(context, impRecord, MetadataSchemaEnum.DC.getName(), "title", - null, null, "New Test publication", 0); + null, null, "New Test publication", null); createImpMetadatavalue(context, impRecord, MetadataSchemaEnum.DC.getName(), "contributor", - "author", null, "John Smith", 0); + "author", null, "John Smith", null); context.restoreAuthSystemState(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/matcher/CustomItemMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/matcher/CustomItemMatcher.java new file mode 100644 index 000000000000..346ab43d1732 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/matcher/CustomItemMatcher.java @@ -0,0 +1,26 @@ +/** + * 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.matcher; + +import java.util.UUID; + +import org.dspace.content.Item; +import org.mockito.ArgumentMatcher; + +public class CustomItemMatcher implements ArgumentMatcher { + private final UUID expectedUUID; + + public CustomItemMatcher(UUID expectedUUID) { + this.expectedUUID = expectedUUID; + } + + @Override + public boolean matches(Item actual) { + return actual != null && actual.getID().equals(expectedUUID); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/matcher/MetadataValueMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/matcher/MetadataValueMatcher.java deleted file mode 100644 index 20295859c6fc..000000000000 --- a/dspace-server-webapp/src/test/java/org/dspace/app/matcher/MetadataValueMatcher.java +++ /dev/null @@ -1,96 +0,0 @@ -/** - * 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.matcher; - -import java.util.Objects; - -import org.dspace.content.MetadataValue; -import org.hamcrest.Description; -import org.hamcrest.TypeSafeMatcher; - -/** - * Implementation of {@link org.hamcrest.Matcher} to match a MetadataValue by - * all its attributes. - * - * @author Luca Giamminonni (luca.giamminonni at 4science.it) - * - */ -public class MetadataValueMatcher extends TypeSafeMatcher { - - private String field; - - private String value; - - private String language; - - private String authority; - - private Integer place; - - private Integer confidence; - - private MetadataValueMatcher(String field, String value, String language, String authority, Integer place, - Integer confidence) { - - this.field = field; - this.value = value; - this.language = language; - this.authority = authority; - this.place = place; - this.confidence = confidence; - - } - - @Override - public void describeTo(Description description) { - description.appendText("MetadataValue with the following attributes [field=" + field + ", value=" - + value + ", language=" + language + ", authority=" + authority + ", place=" + place + ", confidence=" - + confidence + "]"); - } - - @Override - protected void describeMismatchSafely(MetadataValue item, Description mismatchDescription) { - mismatchDescription.appendText("was ") - .appendValue("MetadataValue [metadataField=").appendValue(item.getMetadataField().toString('.')) - .appendValue(", value=").appendValue(item.getValue()).appendValue(", language=").appendValue(language) - .appendValue(", place=").appendValue(item.getPlace()).appendValue(", authority=") - .appendValue(item.getAuthority()).appendValue(", confidence=").appendValue(item.getConfidence() + "]"); - } - - @Override - protected boolean matchesSafely(MetadataValue metadataValue) { - return Objects.equals(metadataValue.getValue(), value) && - Objects.equals(metadataValue.getMetadataField().toString('.'), field) && - Objects.equals(metadataValue.getLanguage(), language) && - Objects.equals(metadataValue.getAuthority(), authority) && - Objects.equals(metadataValue.getPlace(), place) && - Objects.equals(metadataValue.getConfidence(), confidence); - } - - public static MetadataValueMatcher with(String field, String value, String language, - String authority, Integer place, Integer confidence) { - return new MetadataValueMatcher(field, value, language, authority, place, confidence); - } - - public static MetadataValueMatcher with(String field, String value) { - return with(field, value, null, null, 0, -1); - } - - public static MetadataValueMatcher with(String field, String value, String authority, int place, int confidence) { - return with(field, value, null, authority, place, confidence); - } - - public static MetadataValueMatcher with(String field, String value, String authority, int confidence) { - return with(field, value, null, authority, 0, confidence); - } - - public static MetadataValueMatcher with(String field, String value, int place) { - return with(field, value, null, null, place, -1); - } - -} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrisLayoutBoxServiceImplIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrisLayoutBoxServiceImplIT.java new file mode 100644 index 000000000000..b0f14664868b --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CrisLayoutBoxServiceImplIT.java @@ -0,0 +1,92 @@ +/** + * 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.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.layout.CrisLayoutBox; +import org.dspace.layout.CrisLayoutBoxTypes; +import org.dspace.layout.service.impl.CrisLayoutBoxServiceImpl; +import org.dspace.versioning.service.VersioningService; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Unit tests for CrisLayoutBoxServiceImpl, so far only findByItem method is + * tested. + * + * @author Corrado Lombardi (corrado.lombardi at 4science.it) + */ + +public class CrisLayoutBoxServiceImplIT extends AbstractControllerIntegrationTest { + + @Autowired + private CrisLayoutBoxServiceImpl crisLayoutBoxService; + + @Autowired + private VersioningService versioningService; + + @Test + public void testHasContentVersioningBox() { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).build(); + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + + Item item = ItemBuilder + .createItem(context, col) + .withTitle("test") + .build(); + + versioningService.createNewVersion(context, item); + + CrisLayoutBox box = crisLayoutVersioningBox("versioning"); + + assertThat(crisLayoutBoxService.hasContent(context, box, item), is(true)); + context.restoreAuthSystemState(); + } + + @Test + public void testHasNoContentVersioningBox() { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).build(); + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Test collection") + .build(); + + Item item = ItemBuilder + .createItem(context, col) + .withTitle("test") + .build(); + + CrisLayoutBox box = crisLayoutVersioningBox("versioning"); + + assertThat(crisLayoutBoxService.hasContent(context, box, item), is(false)); + context.restoreAuthSystemState(); + } + + private CrisLayoutBox crisLayoutVersioningBox(String shortname) { + return crisLayoutBox(shortname, CrisLayoutBoxTypes.VERSIONING.name()); + } + + private CrisLayoutBox crisLayoutBox(String shortname, String boxType) { + CrisLayoutBox box = new CrisLayoutBox(); + box.setShortname(shortname); + box.setType(boxType); + return box; + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EditItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EditItemRestRepositoryIT.java index 7a511f662fc3..3e8a8e18b44d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EditItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EditItemRestRepositoryIT.java @@ -1951,6 +1951,125 @@ public void testValidationWithHiddenSteps() throws Exception { .andExpect(jsonPath("$.errors[0].paths", contains("/sections/test-outside-workflow-hidden/dc.title"))); } + @Test + public void testPatchWithValidationErrors3213() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("Funding") + .withSubmissionDefinition("modeA") + .withName("Collection 1") + .build(); + + Collection orgunitCollection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .withEntityType("OrgUnit") + .withSubmissionDefinition("orgunit") + .build(); + + Item itemA = ItemBuilder.createItem(context, collection) + .withTitle("Title item") + .withIssueDate("2015-06-25") + .withAuthor("Wayne, Bruce") + .grantLicense() + .build(); + + Item orgUnit = ItemBuilder.createItem(context, orgunitCollection) + .withTitle("OrgUnit") + .withIssueDate("1957") + .grantLicense() + .build(); + + EditItem editItem = new EditItem(context, itemA); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + List list = new ArrayList<>(); + List> funderValues = new ArrayList<>(); + Map funderValuesMap = new HashMap<>(); + List> currencyValues = new ArrayList<>(); + Map currencyMap = new HashMap<>(); + List> amountValues = new ArrayList<>(); + Map amountMap = new HashMap<>(); + funderValuesMap.put("value", "OrgUnit"); + funderValuesMap.put("authority", orgUnit.getID().toString()); + funderValues.add(funderValuesMap); + currencyMap.put("value", "Euro"); + currencyValues.add(currencyMap); + amountMap.put("value", "12312"); + amountValues.add(amountMap); + list.add(new AddOperation("/sections/funding/oairecerif.funder", funderValues)); + list.add(new AddOperation("/sections/funding/oairecerif.amount.currency", currencyValues)); + list.add(new AddOperation("/sections/funding/oairecerif.amount", amountValues)); + + String patchBody = getPatchContent(list); + getClient(tokenAdmin).perform(patch("/api/core/edititems/" + editItem.getID() + ":FIRST") + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.funding['oairecerif.funder'][0].value", + is("OrgUnit"))) + .andExpect(jsonPath("$.sections.funding['oairecerif.funder'][0].authority", + is(orgUnit.getID().toString()))) + .andExpect(jsonPath("$.sections.funding['oairecerif.amount.currency'][0].value", + is("Euro"))) + .andExpect(jsonPath("$.sections.funding['oairecerif.amount'][0].value", + is("12312"))); + } + + @Test + public void testPatchWithValidationErrors32() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .withEntityType("Funding") + .withSubmissionDefinition("modeA") + .withName("Collection 1") + .build(); + + Item itemA = ItemBuilder.createItem(context, collection) + .withIssueDate("2015-06-25") + .withAuthor("Wayne, Bruce") + .grantLicense() + .build(); + + EditItem editItem = new EditItem(context, itemA); + + context.restoreAuthSystemState(); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + + List list = new ArrayList<>(); + List> currencyValues = new ArrayList<>(); + Map currencyMap = new HashMap<>(); + List> amountValues = new ArrayList<>(); + Map amountMap = new HashMap<>(); + currencyMap.put("value", "Euro"); + currencyValues.add(currencyMap); + amountMap.put("value", "12312"); + amountValues.add(amountMap); + list.add(new AddOperation("/sections/funding/oairecerif.amount.currency", currencyValues)); + list.add(new AddOperation("/sections/funding/oairecerif.amount", amountValues)); + + String patchBody = getPatchContent(list); + getClient(tokenAdmin).perform(patch("/api/core/edititems/" + editItem.getID() + ":FIRST") + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isUnprocessableEntity()) + .andExpect(jsonPath("$.[0].message", is("error.validation.required"))) + .andExpect(jsonPath("$.[0].paths[0]", is("/sections/funding/dc.title"))); + } + private Bitstream getBitstream(Item item, String name) throws SQLException { return bitstreamService.getBitstreamByName(item, "ORIGINAL", name); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index f3877445895a..b261d4b65fed 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -4166,6 +4166,181 @@ public void testDeletionOfFundingToBeSynchronizedWithOrcid() throws Exception { } + @Test + public void testDeletionOfProductToBeSynchronizedWithOrcid() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection profileCollection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Profiles") + .withEntityType("Person") + .build(); + + Collection productCollection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Products") + .withEntityType("Product") + .build(); + + EPerson firstOwner = EPersonBuilder.createEPerson(context) + .withEmail("owner2@test.com") + .build(); + + EPerson secondOwner = EPersonBuilder.createEPerson(context) + .withEmail("owner3@test.com") + .build(); + + EPerson thirdOwner = EPersonBuilder.createEPerson(context) + .withEmail("owner1@test.com") + .build(); + + Item firstProfile = ItemBuilder.createItem(context, profileCollection) + .withTitle("Test User") + .withDspaceObjectOwner(firstOwner.getFullName(), firstOwner.getID().toString()) + .withOrcidIdentifier("0000-1111-2222-3333") + .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", firstOwner) + .withOrcidSynchronizationProductsPreference(ALL) + .build(); + + Item secondProfile = ItemBuilder.createItem(context, profileCollection) + .withTitle("Test User") + .withDspaceObjectOwner(secondOwner.getFullName(), secondOwner.getID().toString()) + .withOrcidIdentifier("4444-1111-2222-3333") + .withOrcidAccessToken("bb4d18a0-8d9a-40f1-b601-a417255c8d20", secondOwner) + .build(); + + Item thirdProfile = ItemBuilder.createItem(context, profileCollection) + .withTitle("Test User") + .withDspaceObjectOwner(thirdOwner.getFullName(), thirdOwner.getID().toString()) + .withOrcidIdentifier("5555-1111-2222-3333") + .withOrcidAccessToken("cb4d18a0-8d9a-40f1-b601-a417255c8d20", thirdOwner) + .withOrcidSynchronizationProductsPreference(ALL) + .build(); + + Item product = ItemBuilder.createItem(context, productCollection) + .withTitle("Test product") + + .build(); + + createOrcidQueue(context, firstProfile, product).build(); + createOrcidQueue(context, secondProfile, product).build(); + + List historyRecords = new ArrayList<>(); + historyRecords.add(createOrcidHistory(context, firstProfile, product).build()); + historyRecords.add(createOrcidHistory(context, firstProfile, product).withPutCode("12345").build()); + historyRecords.add(createOrcidHistory(context, secondProfile, product).build()); + historyRecords.add(createOrcidHistory(context, secondProfile, product).withPutCode("67891").build()); + historyRecords.add(createOrcidHistory(context, thirdProfile, product).build()); + + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(delete("/api/core/items/" + product.getID())) + .andExpect(status().is(204)); + + List orcidQueueRecords = orcidQueueService.findAll(context); + assertThat(orcidQueueRecords, hasSize(1)); + assertThat(orcidQueueRecords, hasItem(matches(firstProfile, null, "Product", "12345", DELETE))); + + for (OrcidHistory historyRecord : historyRecords) { + historyRecord = context.reloadEntity(historyRecord); + assertThat(historyRecord, notNullValue()); + assertThat(historyRecord.getEntity(), nullValue()); + } + + } + + @Test + public void testDeletionOfPatentToBeSynchronizedWithOrcid() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection profileCollection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Profiles") + .withEntityType("Person") + .build(); + + Collection patentCollection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Patents") + .withEntityType("Patent") + .build(); + + EPerson firstOwner = EPersonBuilder.createEPerson(context) + .withEmail("owner2@test.com") + .build(); + + EPerson secondOwner = EPersonBuilder.createEPerson(context) + .withEmail("owner3@test.com") + .build(); + + EPerson thirdOwner = EPersonBuilder.createEPerson(context) + .withEmail("owner1@test.com") + .build(); + + Item firstProfile = ItemBuilder.createItem(context, profileCollection) + .withTitle("Test User") + .withDspaceObjectOwner(firstOwner.getFullName(), firstOwner.getID().toString()) + .withOrcidIdentifier("0000-1111-2222-3333") + .withOrcidAccessToken("ab4d18a0-8d9a-40f1-b601-a417255c8d20", firstOwner) + .withOrcidSynchronizationPatentsPreference(ALL) + .build(); + + Item secondProfile = ItemBuilder.createItem(context, profileCollection) + .withTitle("Test User") + .withDspaceObjectOwner(secondOwner.getFullName(), secondOwner.getID().toString()) + .withOrcidIdentifier("4444-1111-2222-3333") + .withOrcidAccessToken("bb4d18a0-8d9a-40f1-b601-a417255c8d20", secondOwner) + .build(); + + Item thirdProfile = ItemBuilder.createItem(context, profileCollection) + .withTitle("Test User") + .withDspaceObjectOwner(thirdOwner.getFullName(), thirdOwner.getID().toString()) + .withOrcidIdentifier("5555-1111-2222-3333") + .withOrcidAccessToken("cb4d18a0-8d9a-40f1-b601-a417255c8d20", thirdOwner) + .withOrcidSynchronizationPatentsPreference(ALL) + .build(); + + Item patent = ItemBuilder.createItem(context, patentCollection) + .withTitle("Test patent") + .build(); + + createOrcidQueue(context, firstProfile, patent).build(); + createOrcidQueue(context, secondProfile, patent).build(); + + List historyRecords = new ArrayList<>(); + historyRecords.add(createOrcidHistory(context, firstProfile, patent).build()); + historyRecords.add(createOrcidHistory(context, firstProfile, patent).withPutCode("12345").build()); + historyRecords.add(createOrcidHistory(context, secondProfile, patent).build()); + historyRecords.add(createOrcidHistory(context, secondProfile, patent).withPutCode("67891").build()); + historyRecords.add(createOrcidHistory(context, thirdProfile, patent).build()); + + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(delete("/api/core/items/" + patent.getID())) + .andExpect(status().is(204)); + + List orcidQueueRecords = orcidQueueService.findAll(context); + assertThat(orcidQueueRecords, hasSize(1)); + assertThat(orcidQueueRecords, hasItem(matches(firstProfile, null, "Patent", "12345", DELETE))); + + for (OrcidHistory historyRecord : historyRecords) { + historyRecord = context.reloadEntity(historyRecord); + assertThat(historyRecord, notNullValue()); + assertThat(historyRecord.getEntity(), nullValue()); + } + + } + private void initPublicationAuthorsRelationships() throws SQLException { context.turnOffAuthorisationSystem(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java index 092b4e47479a..53e6abeda208 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResearcherProfileRestRepositoryIT.java @@ -58,6 +58,7 @@ import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.RemoveOperation; import org.dspace.app.rest.model.patch.ReplaceOperation; +import org.dspace.app.rest.repository.ResearcherProfileRestRepository; import org.dspace.app.rest.repository.patch.operation.ResearcherProfileAddOrcidOperation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authorize.AuthorizeException; @@ -1171,6 +1172,8 @@ public void testOrcidMetadataOfEpersonAreCopiedOnProfile() throws Exception { .andExpect(jsonPath("$.orcidSynchronization.mode", is("MANUAL"))) .andExpect(jsonPath("$.orcidSynchronization.publicationsPreference", is("DISABLED"))) .andExpect(jsonPath("$.orcidSynchronization.fundingsPreference", is("DISABLED"))) + .andExpect(jsonPath("$.orcidSynchronization.productsPreference", is("DISABLED"))) + .andExpect(jsonPath("$.orcidSynchronization.patentsPreference", is("DISABLED"))) .andExpect(jsonPath("$.orcidSynchronization.profilePreferences", empty())); String itemId = getItemIdByProfileId(authToken, ePersonId); @@ -1307,6 +1310,124 @@ public void testPatchToSetOrcidSynchronizationPreferenceForFundings() throws Exc } + @Test + public void testPatchToSetOrcidSynchronizationPreferenceForProduct() throws Exception { + + context.turnOffAuthorisationSystem(); + + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withOrcid("0000-1111-2222-3333") + .withEmail("test@email.it") + .withPassword(password) + .withNameInMetadata("Test", "User") + .withOrcidScope("/first-scope") + .withOrcidScope("/second-scope") + .build(); + + OrcidTokenBuilder.create(context, ePerson, "af097328-ac1c-4a3e-9eb4-069897874910").build(); + + context.restoreAuthSystemState(); + + String ePersonId = ePerson.getID().toString(); + String authToken = getAuthToken(ePerson.getEmail(), password); + + getClient(authToken).perform(post("/api/eperson/profiles/") + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()); + + List operations = asList(new ReplaceOperation("/orcid/products", ALL.name())); + + getClient(authToken).perform(patch("/api/eperson/profiles/{id}", ePersonId) + .content(getPatchContent(operations)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.orcidSynchronization.productsPreference", is(ALL.name()))); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}", ePersonId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.orcidSynchronization.productsPreference", is(ALL.name()))); + + operations = asList(new ReplaceOperation("/orcid/products", MINE.name())); + + getClient(authToken).perform(patch("/api/eperson/profiles/{id}", ePersonId) + .content(getPatchContent(operations)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.orcidSynchronization.productsPreference", is(MINE.name()))); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}", ePersonId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.orcidSynchronization.productsPreference", is(MINE.name()))); + + operations = asList(new ReplaceOperation("/orcid/products", "INVALID_VALUE")); + + getClient(authToken).perform(patch("/api/eperson/profiles/{id}", ePersonId) + .content(getPatchContent(operations)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isUnprocessableEntity()); + + } + + @Test + public void testPatchToSetOrcidSynchronizationPreferenceForPatent() throws Exception { + + context.turnOffAuthorisationSystem(); + + EPerson ePerson = EPersonBuilder.createEPerson(context) + .withCanLogin(true) + .withOrcid("0000-1111-2222-3333") + .withEmail("test@email.it") + .withPassword(password) + .withNameInMetadata("Test", "User") + .withOrcidScope("/first-scope") + .withOrcidScope("/second-scope") + .build(); + + OrcidTokenBuilder.create(context, ePerson, "af097328-ac1c-4a3e-9eb4-069897874910").build(); + + context.restoreAuthSystemState(); + + String ePersonId = ePerson.getID().toString(); + String authToken = getAuthToken(ePerson.getEmail(), password); + + getClient(authToken).perform(post("/api/eperson/profiles/") + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isCreated()); + + List operations = asList(new ReplaceOperation("/orcid/patents", ALL.name())); + + getClient(authToken).perform(patch("/api/eperson/profiles/{id}", ePersonId) + .content(getPatchContent(operations)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.orcidSynchronization.patentsPreference", is(ALL.name()))); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}", ePersonId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.orcidSynchronization.patentsPreference", is(ALL.name()))); + + operations = asList(new ReplaceOperation("/orcid/patents", MINE.name())); + + getClient(authToken).perform(patch("/api/eperson/profiles/{id}", ePersonId) + .content(getPatchContent(operations)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.orcidSynchronization.patentsPreference", is(MINE.name()))); + + getClient(authToken).perform(get("/api/eperson/profiles/{id}", ePersonId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.orcidSynchronization.patentsPreference", is(MINE.name()))); + + operations = asList(new ReplaceOperation("/orcid/patents", "INVALID_VALUE")); + + getClient(authToken).perform(patch("/api/eperson/profiles/{id}", ePersonId) + .content(getPatchContent(operations)) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isUnprocessableEntity()); + + } + @Test public void testPatchToSetOrcidSynchronizationPreferenceForProfile() throws Exception { @@ -2125,14 +2246,14 @@ public void testOrcidSynchronizationPreferenceUpdateForceOrcidQueueRecalculation context.turnOffAuthorisationSystem(); EPerson ePerson = EPersonBuilder.createEPerson(context) - .withCanLogin(true) - .withOrcid("0000-1111-2222-3333") - .withOrcidScope("/read") - .withOrcidScope("/write") - .withEmail("test@email.it") - .withPassword(password) - .withNameInMetadata("Test", "User") - .build(); + .withCanLogin(true) + .withOrcid("0000-1111-2222-3333") + .withOrcidScope("/read") + .withOrcidScope("/write") + .withEmail("test@email.it") + .withPassword(password) + .withNameInMetadata("Test", "User") + .build(); OrcidTokenBuilder.create(context, ePerson, "3de2e370-8aa9-4bbe-8d7e-f5b1577bdad4").build(); @@ -2144,13 +2265,22 @@ public void testOrcidSynchronizationPreferenceUpdateForceOrcidQueueRecalculation Collection publications = createCollection("Publications", "Publication"); + Collection products = createCollection("Products", "Product"); + Item publication = createPublication(publications, "Test publication", profile); + Item product = createProduct(products, "Test product", profile); + Collection fundings = createCollection("Fundings", "Funding"); Item firstFunding = createFundingWithInvestigator(fundings, "First funding", profile); Item secondFunding = createFundingWithCoInvestigator(fundings, "Second funding", profile); + Collection patents = createCollection("Patents", "Patent"); + + Item firstPatent = createPatent(patents, "First patent", profile); + Item secondPatent = createPatent(patents, "Second patent", profile); + context.restoreAuthSystemState(); // no preferences configured, so no orcid queue records created @@ -2159,18 +2289,20 @@ public void testOrcidSynchronizationPreferenceUpdateForceOrcidQueueRecalculation String authToken = getAuthToken(ePerson.getEmail(), password); getClient(authToken).perform(patch("/api/eperson/profiles/{id}", ePersonId.toString()) - .content(getPatchContent(asList(new ReplaceOperation("/orcid/publications", "ALL")))) - .contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(status().isOk()); + .content(getPatchContent( + asList(new ReplaceOperation("/orcid/publications", "ALL")))) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()); List queueRecords = orcidQueueService.findByProfileItemId(context, profileItemId); assertThat(queueRecords, hasSize(1)); assertThat(queueRecords, has(orcidQueueRecordWithEntity(publication))); getClient(authToken).perform(patch("/api/eperson/profiles/{id}", ePersonId.toString()) - .content(getPatchContent(asList(new ReplaceOperation("/orcid/fundings", "ALL")))) - .contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(status().isOk()); + .content( + getPatchContent(asList(new ReplaceOperation("/orcid/fundings", "ALL")))) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()); queueRecords = orcidQueueService.findByProfileItemId(context, profileItemId); assertThat(queueRecords, hasSize(3)); @@ -2179,28 +2311,85 @@ public void testOrcidSynchronizationPreferenceUpdateForceOrcidQueueRecalculation assertThat(queueRecords, has(orcidQueueRecordWithEntity(secondFunding))); getClient(authToken).perform(patch("/api/eperson/profiles/{id}", ePersonId.toString()) - .content(getPatchContent(asList(new ReplaceOperation("/orcid/publications", "DISABLED")))) - .contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(status().isOk()); + .content( + getPatchContent(asList(new ReplaceOperation("/orcid/products", "ALL")))) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()); queueRecords = orcidQueueService.findByProfileItemId(context, profileItemId); - assertThat(queueRecords, hasSize(2)); + assertThat(queueRecords, hasSize(4)); + assertThat(queueRecords, has(orcidQueueRecordWithEntity(publication))); assertThat(queueRecords, has(orcidQueueRecordWithEntity(firstFunding))); assertThat(queueRecords, has(orcidQueueRecordWithEntity(secondFunding))); + assertThat(queueRecords, has(orcidQueueRecordWithEntity(product))); getClient(authToken).perform(patch("/api/eperson/profiles/{id}", ePersonId.toString()) - .content(getPatchContent(asList(new ReplaceOperation("/orcid/fundings", "DISABLED")))) - .contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(status().isOk()); + .content( + getPatchContent(asList(new ReplaceOperation("/orcid/patents", "ALL")))) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()); + + queueRecords = orcidQueueService.findByProfileItemId(context, profileItemId); + assertThat(queueRecords, hasSize(6)); + assertThat(queueRecords, has(orcidQueueRecordWithEntity(publication))); + assertThat(queueRecords, has(orcidQueueRecordWithEntity(firstFunding))); + assertThat(queueRecords, has(orcidQueueRecordWithEntity(secondFunding))); + assertThat(queueRecords, has(orcidQueueRecordWithEntity(product))); + assertThat(queueRecords, has(orcidQueueRecordWithEntity(firstPatent))); + assertThat(queueRecords, has(orcidQueueRecordWithEntity(secondPatent))); + + getClient(authToken).perform(patch("/api/eperson/profiles/{id}", ePersonId.toString()) + .content(getPatchContent( + asList(new ReplaceOperation("/orcid/publications", "DISABLED")))) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()); + + queueRecords = orcidQueueService.findByProfileItemId(context, profileItemId); + assertThat(queueRecords, hasSize(5)); + assertThat(queueRecords, has(orcidQueueRecordWithEntity(firstFunding))); + assertThat(queueRecords, has(orcidQueueRecordWithEntity(secondFunding))); + assertThat(queueRecords, has(orcidQueueRecordWithEntity(product))); + assertThat(queueRecords, has(orcidQueueRecordWithEntity(firstPatent))); + assertThat(queueRecords, has(orcidQueueRecordWithEntity(secondPatent))); + + getClient(authToken).perform(patch("/api/eperson/profiles/{id}", ePersonId.toString()) + .content(getPatchContent( + asList(new ReplaceOperation("/orcid/fundings", "DISABLED")))) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()); + + queueRecords = orcidQueueService.findByProfileItemId(context, profileItemId); + assertThat(queueRecords, hasSize(3)); + assertThat(queueRecords, has(orcidQueueRecordWithEntity(product))); + assertThat(queueRecords, has(orcidQueueRecordWithEntity(firstPatent))); + assertThat(queueRecords, has(orcidQueueRecordWithEntity(secondPatent))); + + getClient(authToken).perform(patch("/api/eperson/profiles/{id}", ePersonId.toString()) + .content(getPatchContent( + asList(new ReplaceOperation("/orcid/products", "DISABLED")))) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()); + + queueRecords = orcidQueueService.findByProfileItemId(context, profileItemId); + assertThat(queueRecords, hasSize(2)); + assertThat(queueRecords, has(orcidQueueRecordWithEntity(firstPatent))); + assertThat(queueRecords, has(orcidQueueRecordWithEntity(secondPatent))); + + getClient(authToken).perform(patch("/api/eperson/profiles/{id}", ePersonId.toString()) + .content(getPatchContent( + asList(new ReplaceOperation("/orcid/patents", "DISABLED")))) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()); assertThat(orcidQueueService.findByProfileItemId(context, profileItemId), empty()); configurationService.setProperty("orcid.linkable-metadata-fields.ignore", "crisfund.coinvestigators"); getClient(authToken).perform(patch("/api/eperson/profiles/{id}", ePersonId.toString()) - .content(getPatchContent(asList(new ReplaceOperation("/orcid/fundings", "ALL")))) - .contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(status().isOk()); + .content( + getPatchContent(asList(new ReplaceOperation("/orcid/fundings", "ALL")))) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()); queueRecords = orcidQueueService.findByProfileItemId(context, profileItemId); assertThat(queueRecords, hasSize(1)); @@ -2208,9 +2397,10 @@ public void testOrcidSynchronizationPreferenceUpdateForceOrcidQueueRecalculation // verify that no ORCID queue recalculation is done if the preference does not change getClient(authToken).perform(patch("/api/eperson/profiles/{id}", ePersonId.toString()) - .content(getPatchContent(asList(new ReplaceOperation("/orcid/fundings", "ALL")))) - .contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(status().isOk()); + .content( + getPatchContent(asList(new ReplaceOperation("/orcid/fundings", "ALL")))) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()); List newRecords = orcidQueueService.findByProfileItemId(context, profileItemId); assertThat(newRecords, hasSize(1)); @@ -2218,6 +2408,7 @@ public void testOrcidSynchronizationPreferenceUpdateForceOrcidQueueRecalculation } + @Test public void researcherProfileClaim() throws Exception { String id = user.getID().toString(); @@ -2615,6 +2806,20 @@ private Item createPublication(Collection collection, String title, Item author) .build(); } + private Item createProduct(Collection collection, String title, Item author) { + return ItemBuilder.createItem(context, collection) + .withTitle(title) + .withAuthor(author.getName(), author.getID().toString()) + .build(); + } + + private Item createPatent(Collection collection, String title, Item author) { + return ItemBuilder.createItem(context, collection) + .withTitle(title) + .withAuthor(author.getName(), author.getID().toString()) + .build(); + } + private Item createFundingWithInvestigator(Collection collection, String title, Item investigator) { return ItemBuilder.createItem(context, collection) .withTitle(title) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java index 269de3aefb9d..743e7def3404 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java @@ -321,10 +321,10 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("page=1"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=18"), Matchers.containsString("size=1")))) + Matchers.containsString("page=19"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(19))) - .andExpect(jsonPath("$.page.totalPages", is(19))) + .andExpect(jsonPath("$.page.totalElements", is(20))) + .andExpect(jsonPath("$.page.totalPages", is(20))) .andExpect(jsonPath("$.page.number", is(0))); getClient(tokenAdmin).perform(get("/api/config/submissiondefinitions") @@ -332,7 +332,7 @@ public void findAllPaginationTest() throws Exception { .param("page", "1")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("patent"))) + .andExpect(jsonPath("$._embedded.submissiondefinitions[0].id", is("funding"))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), Matchers.containsString("page=0"), Matchers.containsString("size=1")))) @@ -347,10 +347,10 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("page=1"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissiondefinitions?"), - Matchers.containsString("page=18"), Matchers.containsString("size=1")))) + Matchers.containsString("page=19"), Matchers.containsString("size=1")))) .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(19))) - .andExpect(jsonPath("$.page.totalPages", is(19))) + .andExpect(jsonPath("$.page.totalElements", is(20))) + .andExpect(jsonPath("$.page.totalPages", is(20))) .andExpect(jsonPath("$.page.number", is(1))); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java index d817b573ff2b..c2201f5792f4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java @@ -73,8 +73,8 @@ public void findAll() throws Exception { .andExpect(content().contentType(contentType)) //The configuration file for the test env includes PAGE_TOTAL_ELEMENTS forms .andExpect(jsonPath("$.page.size", is(20))) - .andExpect(jsonPath("$.page.totalElements", equalTo(40))) - .andExpect(jsonPath("$.page.totalPages", equalTo(2))) + .andExpect(jsonPath("$.page.totalElements", equalTo(43))) + .andExpect(jsonPath("$.page.totalPages", equalTo(3))) .andExpect(jsonPath("$.page.number", is(0))) .andExpect( jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "config/submissionforms"))) @@ -90,8 +90,8 @@ public void findAllWithNewlyCreatedAccountTest() throws Exception { .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.page.size", is(20))) - .andExpect(jsonPath("$.page.totalElements", equalTo(40))) - .andExpect(jsonPath("$.page.totalPages", equalTo(2))) + .andExpect(jsonPath("$.page.totalElements", equalTo(43))) + .andExpect(jsonPath("$.page.totalPages", equalTo(3))) .andExpect(jsonPath("$.page.number", is(0))) .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "config/submissionforms"))) @@ -670,10 +670,10 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("page=1"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissionforms?"), - Matchers.containsString("page=19"), Matchers.containsString("size=2")))) + Matchers.containsString("page=21"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$.page.size", is(2))) - .andExpect(jsonPath("$.page.totalElements", equalTo(40))) - .andExpect(jsonPath("$.page.totalPages", equalTo(20))) + .andExpect(jsonPath("$.page.totalElements", equalTo(43))) + .andExpect(jsonPath("$.page.totalPages", equalTo(22))) .andExpect(jsonPath("$.page.number", is(0))); getClient(tokenAdmin).perform(get("/api/config/submissionforms") @@ -681,8 +681,8 @@ public void findAllPaginationTest() throws Exception { .param("page", "15")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.submissionforms[0].id", is("publication-dc-contributor-author"))) - .andExpect(jsonPath("$._embedded.submissionforms[1].id", is("publication-dc-contributor-editor"))) + .andExpect(jsonPath("$._embedded.submissionforms[0].id", is("traditionalpagethree-cris-collapsed"))) + .andExpect(jsonPath("$._embedded.submissionforms[1].id", is("orange"))) .andExpect(jsonPath("$._links.first.href", Matchers.allOf( Matchers.containsString("/api/config/submissionforms?"), Matchers.containsString("page=0"), Matchers.containsString("size=2")))) @@ -697,10 +697,10 @@ public void findAllPaginationTest() throws Exception { Matchers.containsString("page=16"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissionforms?"), - Matchers.containsString("page=19"), Matchers.containsString("size=2")))) + Matchers.containsString("page=21"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$.page.size", is(2))) - .andExpect(jsonPath("$.page.totalElements", equalTo(40))) - .andExpect(jsonPath("$.page.totalPages", equalTo(20))) + .andExpect(jsonPath("$.page.totalElements", equalTo(43))) + .andExpect(jsonPath("$.page.totalPages", equalTo(22))) .andExpect(jsonPath("$.page.number", is(15))); } @@ -744,10 +744,10 @@ public void visibilityTest() throws Exception { Matchers.containsString("page=4"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$._links.last.href", Matchers.allOf( Matchers.containsString("/api/config/submissionforms?"), - Matchers.containsString("page=19"), Matchers.containsString("size=2")))) + Matchers.containsString("page=21"), Matchers.containsString("size=2")))) .andExpect(jsonPath("$.page.size", is(2))) - .andExpect(jsonPath("$.page.totalElements", equalTo(40))) - .andExpect(jsonPath("$.page.totalPages", equalTo(20))) + .andExpect(jsonPath("$.page.totalElements", equalTo(43))) + .andExpect(jsonPath("$.page.totalPages", equalTo(22))) .andExpect(jsonPath("$.page.number", is(4))); } } 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 1eedda87f990..59fddc0e5fbf 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 @@ -164,11 +164,12 @@ public void findAllTest() throws Exception { VocabularyMatcher.matchProperties("SolrAuthorAuthority", "SolrAuthorAuthority", false, false), VocabularyMatcher.matchProperties("SRJournalTitle", "SRJournalTitle", false, false), VocabularyMatcher.matchProperties("common_types", "common_types", true, false), - VocabularyMatcher.matchProperties("publication-coar-types", "publication-coar-types", false, true) + VocabularyMatcher.matchProperties("publication-coar-types", "publication-coar-types", false, true), + VocabularyMatcher.matchProperties("currency", "currency", true, false) ))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("api/submission/vocabularies"))) - .andExpect(jsonPath("$.page.totalElements", is(10))); + .andExpect(jsonPath("$.page.totalElements", is(11))); } @Test diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java index 026a8d956544..ea4ed736a12f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java @@ -15,6 +15,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; @@ -2707,4 +2708,162 @@ public void testValidationWithHiddenSteps() throws Exception { .andExpect(jsonPath("$.errors[0].paths", contains("/sections/test-outside-workflow-hidden/dc.title"))); } + @Test + /** + * Test the addition of metadata + * + * @throws Exception + */ + public void patchAddMetadataToInlineGroupTypeMetadataShouldReturnErrorsTest() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community with one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .withEntityType("Funding") + .withWorkflowGroup(1, eperson).build(); + + //2. create a normal user to use as submitter + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword("dspace") + .build(); + + context.setCurrentUser(submitter); + + //3. a claimed task with workflow item in edit step + ClaimedTask claimedTask = ClaimedTaskBuilder.createClaimedTask(context, col1, eperson) + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .grantLicense() + .build(); + claimedTask.setStepID("editstep"); + claimedTask.setActionID("editaction"); + XmlWorkflowItem witem = claimedTask.getWorkflowItem(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(eperson.getEmail(), password); + + List list = new ArrayList<>(); + List> currencyValues = new ArrayList<>(); + Map currencyMap = new HashMap<>(); + List> amountValues = new ArrayList<>(); + Map amountMap = new HashMap<>(); + currencyMap.put("value", "Euro"); + currencyValues.add(currencyMap); + amountMap.put("value", "12312"); + amountValues.add(amountMap); + list.add(new AddOperation("/sections/funding/oairecerif.amount.currency", currencyValues)); + list.add(new AddOperation("/sections/funding/oairecerif.amount", amountValues)); + + String patchBody = getPatchContent(list); + getClient(authToken).perform(patch("/api/workflow/workflowitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors[?(@.message=='error.validation.required')]", + contains( + hasJsonPath("$.paths", containsInAnyOrder( + hasJsonPath("$", Matchers.is("/sections/funding/dc.title")), + hasJsonPath("$", Matchers.is("/sections/funding/oairecerif.funder")) + ))))) + .andExpect(jsonPath("$.sections.funding['oairecerif.amount.currency'][0].value", + is("Euro"))) + .andExpect(jsonPath("$.sections.funding['oairecerif.amount'][0].value", + is("12312"))); + + } + + @Test + /** + * Test the addition of metadata + * + * @throws Exception + */ + public void patchAddMetadataToInlineGroupTypeMetadataShouldCompletedWithoutErrorsTest() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community with one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .withEntityType("Funding") + .withWorkflowGroup(1, eperson).build(); + + //2. create a normal user to use as submitter + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail("submitter@example.com") + .withPassword("dspace") + .build(); + + Collection orgunitCollection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .withEntityType("OrgUnit") + .withSubmissionDefinition("orgunit") + .build(); + + context.setCurrentUser(submitter); + + Item orgUnit = ItemBuilder.createItem(context, orgunitCollection) + .withTitle("OrgUnit") + .withIssueDate("1957") + .build(); + + //3. a claimed task with workflow item in edit step + ClaimedTask claimedTask = ClaimedTaskBuilder.createClaimedTask(context, col1, eperson) + .withTitle("Title") + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .grantLicense() + .build(); + claimedTask.setStepID("editstep"); + claimedTask.setActionID("editaction"); + XmlWorkflowItem witem = claimedTask.getWorkflowItem(); + + context.restoreAuthSystemState(); + + String authToken = getAuthToken(eperson.getEmail(), password); + + List list = new ArrayList<>(); + List> funderValues = new ArrayList<>(); + Map funderValuesMap = new HashMap<>(); + List> currencyValues = new ArrayList<>(); + Map currencyMap = new HashMap<>(); + List> amountValues = new ArrayList<>(); + Map amountMap = new HashMap<>(); + funderValuesMap.put("value", "OrgUnit"); + funderValuesMap.put("authority", orgUnit.getID().toString()); + funderValues.add(funderValuesMap); + currencyMap.put("value", "Euro"); + currencyValues.add(currencyMap); + amountMap.put("value", "12312"); + amountValues.add(amountMap); + list.add(new AddOperation("/sections/funding/oairecerif.funder", funderValues)); + list.add(new AddOperation("/sections/funding/oairecerif.amount.currency", currencyValues)); + list.add(new AddOperation("/sections/funding/oairecerif.amount", amountValues)); + + String patchBody = getPatchContent(list); + getClient(authToken).perform(patch("/api/workflow/workflowitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").doesNotExist()) + .andExpect(jsonPath("$.sections.funding['oairecerif.funder'][0].value", + is("OrgUnit"))) + .andExpect(jsonPath("$.sections.funding['oairecerif.funder'][0].authority", + is(orgUnit.getID().toString()))) + .andExpect(jsonPath("$.sections.funding['oairecerif.amount.currency'][0].value", + is("Euro"))) + .andExpect(jsonPath("$.sections.funding['oairecerif.amount'][0].value", + is("12312")));; + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index c6b4821a950f..f895f5f3967b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java @@ -10155,4 +10155,135 @@ public void patchBySupervisorTest() throws Exception { "ExtraEntry") ))); } + + @Test + public void patchAddMetadataToInlineGroupTypeMetadataShouldCompletedWithoutErrorsTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withEntityType("Funding") + .withSubmissionDefinition("funding") + .build(); + + Collection orgunitCollection = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withEntityType("OrgUnit") + .withSubmissionDefinition("orgunit") + .build(); + + String authToken = getAuthToken(eperson.getEmail(), password); + + Item orgUnit = ItemBuilder.createItem(context, orgunitCollection) + .withTitle("OrgUnit") + .withIssueDate("1957") + .build(); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("Test witem") + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .grantLicense() + .build(); + + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + + context.restoreAuthSystemState(); + + List list = new ArrayList<>(); + List> funderValues = new ArrayList<>(); + Map funderValuesMap = new HashMap<>(); + List> currencyValues = new ArrayList<>(); + Map currencyMap = new HashMap<>(); + List> amountValues = new ArrayList<>(); + Map amountMap = new HashMap<>(); + funderValuesMap.put("value", "OrgUnit"); + funderValuesMap.put("authority", orgUnit.getID().toString()); + funderValues.add(funderValuesMap); + currencyMap.put("value", "Euro"); + currencyValues.add(currencyMap); + amountMap.put("value", "12312"); + amountValues.add(amountMap); + list.add(new AddOperation("/sections/funding/oairecerif.funder", funderValues)); + list.add(new AddOperation("/sections/funding/oairecerif.amount.currency", currencyValues)); + list.add(new AddOperation("/sections/funding/oairecerif.amount", amountValues)); + + String patchBody = getPatchContent(list); + getClient(authToken).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.funding['oairecerif.funder'][0].value", + is("OrgUnit"))) + .andExpect(jsonPath("$.sections.funding['oairecerif.funder'][0].authority", + is(orgUnit.getID().toString()))) + .andExpect(jsonPath("$.sections.funding['oairecerif.amount.currency'][0].value", + is("Euro"))) + .andExpect(jsonPath("$.sections.funding['oairecerif.amount'][0].value", + is("12312"))); + } + + @Test + public void patchAddMetadataToInlineGroupTypeMetadataShouldReturnErrorsTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withEntityType("Funding") + .withSubmissionDefinition("funding") + .build(); + + String authToken = getAuthToken(eperson.getEmail(), password); + + WorkspaceItem witem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("Test witem") + .withIssueDate("2017-10-17") + .withSubject("ExtraEntry") + .grantLicense() + .build(); + + //disable file upload mandatory + configurationService.setProperty("webui.submit.upload.required", false); + + context.restoreAuthSystemState(); + + List list = new ArrayList<>(); + List> currencyValues = new ArrayList<>(); + Map currencyMap = new HashMap<>(); + List> amountValues = new ArrayList<>(); + Map amountMap = new HashMap<>(); + currencyMap.put("value", "Euro"); + currencyValues.add(currencyMap); + amountMap.put("value", "12312"); + amountValues.add(amountMap); + list.add(new AddOperation("/sections/funding/oairecerif.amount.currency", currencyValues)); + list.add(new AddOperation("/sections/funding/oairecerif.amount", amountValues)); + + String patchBody = getPatchContent(list); + getClient(authToken).perform(patch("/api/submission/workspaceitems/" + witem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(jsonPath("$.errors[?(@.message=='error.validation.required')]", + contains( + hasJsonPath("$.paths", contains( + hasJsonPath("$", Matchers.is("/sections/funding/oairecerif.funder")) + ))))) + .andExpect(jsonPath("$.sections.funding['oairecerif.amount.currency'][0].value", + is("Euro"))) + .andExpect(jsonPath("$.sections.funding['oairecerif.amount'][0].value", + is("12312"))); + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/CrisLayoutBoxConverterIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/CrisLayoutBoxConverterIT.java index 114870dcdf3d..6bde56da05d4 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/CrisLayoutBoxConverterIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/CrisLayoutBoxConverterIT.java @@ -16,6 +16,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertEquals; import java.util.List; @@ -38,6 +39,7 @@ import org.dspace.content.service.MetadataFieldService; import org.dspace.content.service.MetadataSchemaService; import org.dspace.layout.CrisLayoutBox; +import org.dspace.layout.CrisLayoutBoxTypes; import org.dspace.layout.CrisLayoutField; import org.dspace.layout.LayoutSecurity; import org.junit.Test; @@ -312,4 +314,43 @@ public void testMetricsBoxConversion() throws Exception { assertThat(config.getMaxColumns(), is(1)); assertThat(config.getMetrics(), contains("metric1", "metric2")); } + + @Test + public void testVersioningBoxConversion() throws Exception { + + context.turnOffAuthorisationSystem(); + + EntityType entityType = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build(); + + CrisLayoutBox box = CrisLayoutBoxBuilder.createBuilder(context, entityType, true, true) + .withContainer(true) + .withHeader("Box Header") + .withShortname("versioning") + .withType(CrisLayoutBoxTypes.VERSIONING.name()) + .withSecurity(LayoutSecurity.OWNER_ONLY) + .withMaxColumns(1) + .build(); + + CrisLayoutMetric2BoxBuilder.create(context, box, "metric1", 0).build(); + CrisLayoutMetric2BoxBuilder.create(context, box, "metric2", 1).build(); + + context.commit(); + + context.restoreAuthSystemState(); + + CrisLayoutBoxRest rest = converter.convert(box, Projection.DEFAULT); + assertThat(rest, notNullValue()); + assertThat(rest.getBoxType(), is(CrisLayoutBoxTypes.VERSIONING.name())); + assertThat(rest.isContainer(), is(true)); + assertThat(rest.getCollapsed(), is(true)); + assertThat(rest.getEntityType(), is("Publication")); + assertThat(rest.getHeader(), is("Box Header")); + assertThat(rest.getShortname(), is("versioning")); + assertThat(rest.getMaxColumns(), is(1)); + assertThat(rest.getMinor(), is(true)); + assertThat(rest.getSecurity(), is(2)); + assertThat(rest.getStyle(), nullValue()); + assertThat(rest.getMetadataSecurityFields(), empty()); + assertEquals(rest.getConfiguration(), null); + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/enhancer/RelatedItemEnhancerPollerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/enhancer/RelatedItemEnhancerPollerIT.java new file mode 100644 index 000000000000..a710f9882b44 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/enhancer/RelatedItemEnhancerPollerIT.java @@ -0,0 +1,334 @@ +/** + * 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.enhancer; + +import static org.dspace.app.matcher.MetadataValueMatcher.with; +import static org.dspace.app.matcher.MetadataValueMatcher.withNoPlace; +import static org.dspace.core.CrisConstants.PLACEHOLDER_PARENT_METADATA_VALUE; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasSize; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.app.matcher.CustomItemMatcher; +import org.dspace.authorize.AuthorizeException; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.enhancer.service.ItemEnhancerService; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.ItemService; +import org.dspace.core.ReloadableEntity; +import org.dspace.services.ConfigurationService; +import org.dspace.utils.DSpace; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +public class RelatedItemEnhancerPollerIT extends AbstractIntegrationTestWithDatabase { + + private ItemService itemService; + private ItemEnhancerService itemEnhancerService; + private ItemEnhancerService spyItemEnhancerService; + private RelatedItemEnhancerUpdatePoller poller = new RelatedItemEnhancerUpdatePoller(); + private Collection collection; + + @Before + public void setup() throws InterruptedException { + final DSpace dspace = new DSpace(); + ConfigurationService configurationService = dspace.getConfigurationService(); + configurationService.setProperty("item.enable-virtual-metadata", false); + itemService = ContentServiceFactory.getInstance().getItemService(); + itemEnhancerService = dspace.getSingletonService(ItemEnhancerService.class); + spyItemEnhancerService = spy(itemEnhancerService); + poller.setItemEnhancerService(spyItemEnhancerService); + poller.setItemService(itemService); + // cleanup the queue from any items left behind by other tests + poller.pollItemToUpdateAndProcess(); + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + collection = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .build(); + context.restoreAuthSystemState(); + } + + @Test + public void testUpdateRelatedItemAreProcessed() throws Exception { + + context.turnOffAuthorisationSystem(); + + Item person = ItemBuilder.createItem(context, collection) + .withTitle("Walter White") + .withPersonMainAffiliation("4Science") + .build(); + + String personId = person.getID().toString(); + + Item person2 = ItemBuilder.createItem(context, collection) + .withTitle("John Red") + .build(); + + String person2Id = person2.getID().toString(); + + Item person3 = ItemBuilder.createItem(context, collection) + .withTitle("Marc Green") + .withOrcidIdentifier("orcid-person3") + .withPersonMainAffiliation("Affiliation 1") + .withPersonMainAffiliation("Affiliation 2") + .build(); + + String person3Id = person3.getID().toString(); + + Item publication = ItemBuilder.createItem(context, collection) + .withTitle("Test publication") + .withEntityType("Publication") + .withAuthor("Walter White", personId) + .build(); + + Item publication2 = ItemBuilder.createItem(context, collection) + .withTitle("Test publication 2") + .withEntityType("Publication") + .withSubject("test") + .withAuthor("Walter White", personId) + .withAuthor("John Red", person2Id) + .build(); + + Item publication3 = ItemBuilder.createItem(context, collection) + .withTitle("Test publication 3") + .withEntityType("Publication") + .withAuthor("John Red", person2Id) + .withAuthor("Marc Green", person3Id) + .build(); + + context.restoreAuthSystemState(); + publication = context.reloadEntity(publication); + publication2 = context.reloadEntity(publication2); + publication3 = context.reloadEntity(publication3); + + List metadataValues = publication.getMetadata(); + assertThat(metadataValues, hasSize(11)); + assertThat(metadataValues, hasItem(with("cris.virtual.department", "4Science"))); + assertThat(metadataValues, hasItem(with("cris.virtualsource.department", personId))); + assertThat(metadataValues, hasItem(with("cris.virtual.orcid", PLACEHOLDER_PARENT_METADATA_VALUE))); + assertThat(metadataValues, hasItem(with("cris.virtualsource.orcid", personId))); + List metadataValues2 = publication2.getMetadata(); + assertThat(metadataValues2, hasSize(17)); + assertThat(itemService.getMetadataByMetadataString(publication2, "cris.virtual.department"), + containsInAnyOrder( + withNoPlace("cris.virtual.department", "4Science"), + withNoPlace("cris.virtual.department", PLACEHOLDER_PARENT_METADATA_VALUE))); + assertThat(itemService.getMetadataByMetadataString(publication2, "cris.virtualsource.department"), + containsInAnyOrder( + withNoPlace("cris.virtualsource.department", personId), + withNoPlace("cris.virtualsource.department", person2Id))); + assertThat(itemService.getMetadataByMetadataString(publication2, "cris.virtual.orcid"), + containsInAnyOrder( + withNoPlace("cris.virtual.orcid", PLACEHOLDER_PARENT_METADATA_VALUE), + withNoPlace("cris.virtual.orcid", PLACEHOLDER_PARENT_METADATA_VALUE))); + assertThat(itemService.getMetadataByMetadataString(publication2, "cris.virtualsource.orcid"), + containsInAnyOrder( + withNoPlace("cris.virtualsource.orcid", personId), + withNoPlace("cris.virtualsource.orcid", person2Id))); + List metadataValues3 = publication3.getMetadata(); + assertThat(metadataValues3, hasSize(18)); + assertThat(itemService.getMetadataByMetadataString(publication3, "cris.virtual.department"), + containsInAnyOrder( + withNoPlace("cris.virtual.department", PLACEHOLDER_PARENT_METADATA_VALUE), + withNoPlace("cris.virtual.department", "Affiliation 1"), + withNoPlace("cris.virtual.department", "Affiliation 2"))); + assertThat(itemService.getMetadataByMetadataString(publication3, "cris.virtualsource.department"), + containsInAnyOrder( + withNoPlace("cris.virtualsource.department", person2Id), + withNoPlace("cris.virtualsource.department", person3Id), + withNoPlace("cris.virtualsource.department", person3Id))); + assertThat(itemService.getMetadataByMetadataString(publication3, "cris.virtual.orcid"), + containsInAnyOrder( + withNoPlace("cris.virtual.orcid", PLACEHOLDER_PARENT_METADATA_VALUE), + withNoPlace("cris.virtual.orcid", "orcid-person3"))); + assertThat(itemService.getMetadataByMetadataString(publication3, "cris.virtualsource.orcid"), + containsInAnyOrder( + withNoPlace("cris.virtualsource.orcid", person2Id), + withNoPlace("cris.virtualsource.orcid", person3Id))); + + context.turnOffAuthorisationSystem(); + itemService.addMetadata(context, person, "person", "identifier", "orcid", null, "1234-5678-9101"); + itemService.addMetadata(context, person, "person", "affiliation", "name", null, "Company"); + itemService.update(context, person); + context.restoreAuthSystemState(); + person = commitAndReload(person); + Mockito.reset(spyItemEnhancerService); + poller.pollItemToUpdateAndProcess(); + verify(spyItemEnhancerService).enhance(any(), argThat(new CustomItemMatcher(publication.getID())), eq(true)); + verify(spyItemEnhancerService).enhance(any(), argThat(new CustomItemMatcher(publication2.getID())), eq(true)); + // 2 + 1 iteration as the last poll will return null + verify(spyItemEnhancerService, times(3)).pollItemToUpdate(any()); + verify(spyItemEnhancerService).saveAffectedItemsForUpdate(any(), eq(publication.getID())); + verify(spyItemEnhancerService).saveAffectedItemsForUpdate(any(), eq(publication2.getID())); + verifyNoMoreInteractions(spyItemEnhancerService); + person = context.reloadEntity(person); + person2 = context.reloadEntity(person2); + person3 = context.reloadEntity(person3); + publication = context.reloadEntity(publication); + publication2 = context.reloadEntity(publication2); + publication3 = context.reloadEntity(publication3); + + metadataValues = publication.getMetadata(); + assertThat(metadataValues, hasSize(13)); + assertThat(itemService.getMetadataByMetadataString(publication, "cris.virtual.department"), + containsInAnyOrder( + withNoPlace("cris.virtual.department", "4Science"), + withNoPlace("cris.virtual.department", "Company"))); + assertThat(itemService.getMetadataByMetadataString(publication, "cris.virtualsource.department"), + containsInAnyOrder( + withNoPlace("cris.virtualsource.department", personId), + withNoPlace("cris.virtualsource.department", personId))); + assertThat(metadataValues, hasItem(with("cris.virtual.orcid", "1234-5678-9101"))); + assertThat(metadataValues, hasItem(with("cris.virtualsource.orcid", personId))); + metadataValues2 = publication2.getMetadata(); + assertThat(metadataValues2, hasSize(19)); + assertThat(itemService.getMetadataByMetadataString(publication2, "cris.virtual.department"), + containsInAnyOrder( + withNoPlace("cris.virtual.department", "4Science"), + withNoPlace("cris.virtual.department", "Company"), + withNoPlace("cris.virtual.department", PLACEHOLDER_PARENT_METADATA_VALUE))); + assertThat(itemService.getMetadataByMetadataString(publication2, "cris.virtualsource.department"), + containsInAnyOrder( + withNoPlace("cris.virtualsource.department", personId), + withNoPlace("cris.virtualsource.department", personId), + withNoPlace("cris.virtualsource.department", person2Id))); + assertThat(itemService.getMetadataByMetadataString(publication2, "cris.virtual.orcid"), + containsInAnyOrder( + withNoPlace("cris.virtual.orcid", "1234-5678-9101"), + withNoPlace("cris.virtual.orcid", PLACEHOLDER_PARENT_METADATA_VALUE))); + assertThat(itemService.getMetadataByMetadataString(publication2, "cris.virtualsource.orcid"), + containsInAnyOrder( + withNoPlace("cris.virtualsource.orcid", personId), + withNoPlace("cris.virtualsource.orcid", person2Id))); + metadataValues3 = publication3.getMetadata(); + assertThat(metadataValues3, hasSize(18)); + assertThat(itemService.getMetadataByMetadataString(publication3, "cris.virtual.department"), + containsInAnyOrder( + withNoPlace("cris.virtual.department", PLACEHOLDER_PARENT_METADATA_VALUE), + withNoPlace("cris.virtual.department", "Affiliation 1"), + withNoPlace("cris.virtual.department", "Affiliation 2"))); + assertThat(itemService.getMetadataByMetadataString(publication3, "cris.virtualsource.department"), + containsInAnyOrder( + withNoPlace("cris.virtualsource.department", person2Id), + withNoPlace("cris.virtualsource.department", person3Id), + withNoPlace("cris.virtualsource.department", person3Id))); + assertThat(itemService.getMetadataByMetadataString(publication3, "cris.virtual.orcid"), + containsInAnyOrder( + withNoPlace("cris.virtual.orcid", PLACEHOLDER_PARENT_METADATA_VALUE), + withNoPlace("cris.virtual.orcid", "orcid-person3"))); + assertThat(itemService.getMetadataByMetadataString(publication3, "cris.virtualsource.orcid"), + containsInAnyOrder( + withNoPlace("cris.virtualsource.orcid", person2Id), + withNoPlace("cris.virtualsource.orcid", person3Id))); + context.turnOffAuthorisationSystem(); + itemService.clearMetadata(context, person3, "person", "identifier", "orcid", Item.ANY); + itemService.removeMetadataValues(context, person3, + List.of(itemService.getMetadataByMetadataString(person3, "person.affiliation.name").get(0))); + itemService.update(context, person3); + context.restoreAuthSystemState(); + person3 = commitAndReload(person3); + Mockito.reset(spyItemEnhancerService); + poller.pollItemToUpdateAndProcess(); + verify(spyItemEnhancerService).enhance(any(), argThat(new CustomItemMatcher(publication3.getID())), eq(true)); + // 1 + 1 iteration as the last poll will return null + verify(spyItemEnhancerService, times(2)).pollItemToUpdate(any()); + verify(spyItemEnhancerService).saveAffectedItemsForUpdate(any(), eq(publication3.getID())); + verifyNoMoreInteractions(spyItemEnhancerService); + person = context.reloadEntity(person); + person2 = context.reloadEntity(person2); + person3 = context.reloadEntity(person3); + publication = context.reloadEntity(publication); + publication2 = context.reloadEntity(publication2); + publication3 = context.reloadEntity(publication3); + + metadataValues = publication.getMetadata(); + assertThat(metadataValues, hasSize(13)); + assertThat(itemService.getMetadataByMetadataString(publication, "cris.virtual.department"), + containsInAnyOrder( + withNoPlace("cris.virtual.department", "4Science"), + withNoPlace("cris.virtual.department", "Company"))); + assertThat(itemService.getMetadataByMetadataString(publication, "cris.virtualsource.department"), + containsInAnyOrder( + withNoPlace("cris.virtualsource.department", personId), + withNoPlace("cris.virtualsource.department", personId))); + assertThat(metadataValues, hasItem(with("cris.virtual.orcid", "1234-5678-9101"))); + assertThat(metadataValues, hasItem(with("cris.virtualsource.orcid", personId))); + metadataValues2 = publication2.getMetadata(); + assertThat(metadataValues2, hasSize(19)); + assertThat(itemService.getMetadataByMetadataString(publication2, "cris.virtual.department"), + containsInAnyOrder( + withNoPlace("cris.virtual.department", "4Science"), + withNoPlace("cris.virtual.department", "Company"), + withNoPlace("cris.virtual.department", PLACEHOLDER_PARENT_METADATA_VALUE))); + assertThat(itemService.getMetadataByMetadataString(publication2, "cris.virtualsource.department"), + containsInAnyOrder( + withNoPlace("cris.virtualsource.department", personId), + withNoPlace("cris.virtualsource.department", personId), + withNoPlace("cris.virtualsource.department", person2Id))); + assertThat(itemService.getMetadataByMetadataString(publication2, "cris.virtual.orcid"), + containsInAnyOrder( + withNoPlace("cris.virtual.orcid", "1234-5678-9101"), + withNoPlace("cris.virtual.orcid", PLACEHOLDER_PARENT_METADATA_VALUE))); + assertThat(itemService.getMetadataByMetadataString(publication2, "cris.virtualsource.orcid"), + containsInAnyOrder( + withNoPlace("cris.virtualsource.orcid", personId), + withNoPlace("cris.virtualsource.orcid", person2Id))); + metadataValues3 = publication3.getMetadata(); + assertThat(metadataValues3, hasSize(16)); + assertThat(itemService.getMetadataByMetadataString(publication3, "cris.virtual.department"), + containsInAnyOrder( + withNoPlace("cris.virtual.department", PLACEHOLDER_PARENT_METADATA_VALUE), + withNoPlace("cris.virtual.department", "Affiliation 2"))); + assertThat(itemService.getMetadataByMetadataString(publication3, "cris.virtualsource.department"), + containsInAnyOrder( + withNoPlace("cris.virtualsource.department", person2Id), + withNoPlace("cris.virtualsource.department", person3Id))); + assertThat(itemService.getMetadataByMetadataString(publication3, "cris.virtual.orcid"), + containsInAnyOrder( + withNoPlace("cris.virtual.orcid", PLACEHOLDER_PARENT_METADATA_VALUE), + withNoPlace("cris.virtual.orcid", PLACEHOLDER_PARENT_METADATA_VALUE))); + assertThat(itemService.getMetadataByMetadataString(publication3, "cris.virtualsource.orcid"), + containsInAnyOrder( + withNoPlace("cris.virtualsource.orcid", person2Id), + withNoPlace("cris.virtualsource.orcid", person3Id))); + + } + + private List getMetadataValues(Item item, String metadataField) { + return itemService.getMetadataByMetadataString(item, metadataField); + } + + @SuppressWarnings("rawtypes") + private T commitAndReload(T entity) throws SQLException, AuthorizeException { + context.commit(); + return context.reloadEntity(entity); + } + +} diff --git a/dspace/config/crosswalks/orcid/mapConverter-dspace-to-orcid-patent-type.properties b/dspace/config/crosswalks/orcid/mapConverter-dspace-to-orcid-patent-type.properties new file mode 100644 index 000000000000..50aa130ea351 --- /dev/null +++ b/dspace/config/crosswalks/orcid/mapConverter-dspace-to-orcid-patent-type.properties @@ -0,0 +1,3 @@ +# Mapping between DSpace common patent's types and the type supported by ORCID +http\://purl.org/coar/resource_type/c_15cd = patent +http\://purl.org/coar/resource_type/H6QP-SC1X = trademark diff --git a/dspace/config/crosswalks/orcid/mapConverter-dspace-to-orcid-product-type.properties b/dspace/config/crosswalks/orcid/mapConverter-dspace-to-orcid-product-type.properties new file mode 100644 index 000000000000..697ff74646bc --- /dev/null +++ b/dspace/config/crosswalks/orcid/mapConverter-dspace-to-orcid-product-type.properties @@ -0,0 +1,20 @@ +# Mapping between DSpace common product's types and the type supported by ORCID +http\://purl.org/coar/resource_type/c_ddb1 = data-set +http\://purl.org/coar/resource_type/NHD0-W6SY = data-set +http\://purl.org/coar/resource_type/W2XT-7017 = data-set +http\://purl.org/coar/resource_type/CQMR-7K63 = data-set +http\://purl.org/coar/resource_type/FF4C-28RK = data-set +http\://purl.org/coar/resource_type/DD58-GFSX = data-set +http\://purl.org/coar/resource_type/H41Y-FW7B = data-set +http\://purl.org/coar/resource_type/2H0M-X761 = data-set +http\://purl.org/coar/resource_type/A8F1-NPV9 = data-set +http\://purl.org/coar/resource_type/AM6W-6QAW = data-set +http\://purl.org/coar/resource_type/FXF3-D3G7 = data-set +http\://purl.org/coar/resource_type/c_cb28 = data-set +http\://purl.org/coar/resource_type/ACF7-8YT9 = data-set +http\://purl.org/coar/resource_type/c_5ce6 = software +http\://purl.org/coar/resource_type/c_18cc = lecture-speech +http\://purl.org/coar/resource_type/c_18cd = lecture-speech +http\://purl.org/coar/resource_type/c_7ad9 = website +http\://purl.org/coar/resource_type/c_e9a0 = online-resource +http\://purl.org/coar/resource_type/H6QP-SC1X = trademark diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 2e14eeec788c..c200905b263d 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -804,6 +804,11 @@ event.dispatcher.default.class = org.dspace.event.BasicDispatcher # Add orcidqueue here, if the integration with ORCID is configured and wish to enable the synchronization queue functionality event.dispatcher.default.consumers = versioning, discovery, eperson, dedup, crisconsumer, orcidqueue, audit, nbeventsdelete, referenceresolver, orcidwebhook, itemenhancer, customurl, reciprocal, filetypemetadataenhancer +# enable the item enhancer poller +related-item-enhancer-poller.enabled = true +# delay (in ms) between check for item to update due to change in related items used for metadata enrichment +# default 5 seconds +related-item-enhancer-poller.delay = 5000 # The noindex dispatcher will not create search or browse indexes (useful for batch item imports) event.dispatcher.noindex.class = org.dspace.event.BasicDispatcher @@ -1387,6 +1392,15 @@ webui.browse.link.1 = author:dc.contributor.* # webui.browse.metadata.show-freq.3 = false # webui.browse.metadata.show-freq.4 = true +### LOGIN MIUR configuration ### + +### ANCE SEARCH JOURNALS ENDPOINT +#ance.webservice.search.endpoint = https://webservice.cineca.it/ance-ws/services/riviste + +### ANCE REQUEST JOURNALS ENDPOINT +#ance.webservice.addjournal.endpoint = https://webservice.cineca.it/pubblicazioni + + ### i18n - Locales / Language #### # Default Locale # A Locale in the form country or country_language or country_language_variant diff --git a/dspace/config/emails/subscriptions_statistics b/dspace/config/emails/subscriptions_statistics new file mode 100644 index 000000000000..c306d6fc7a5a --- /dev/null +++ b/dspace/config/emails/subscriptions_statistics @@ -0,0 +1,6 @@ +## E-mail sent to designated address about statistics on subscribed items +## +#set($subject = "${config.get('dspace.name')}: Statistics of records which you are subscribed") + +This automatic email is sent by ${config.get('dspace.name')} based on the subscribed statistics updates. +See additional details in the file attached. \ No newline at end of file diff --git a/dspace/config/entities/correction-relationship-types.xml b/dspace/config/entities/correction-relationship-types.xml index adad10029828..31c5a5f7bdc1 100644 --- a/dspace/config/entities/correction-relationship-types.xml +++ b/dspace/config/entities/correction-relationship-types.xml @@ -72,6 +72,34 @@ 1 + + JournalVolume + JournalVolume + isCorrectionOfItem + isCorrectedByItem + + 0 + 1 + + + 0 + 1 + + + + JournalIssue + JournalIssue + isCorrectionOfItem + isCorrectedByItem + + 0 + 1 + + + 0 + 1 + + Publication Publication diff --git a/dspace/config/local.cfg.EXAMPLE b/dspace/config/local.cfg.EXAMPLE index 3028b3b9b24c..c7cab8dd73dd 100644 --- a/dspace/config/local.cfg.EXAMPLE +++ b/dspace/config/local.cfg.EXAMPLE @@ -334,3 +334,36 @@ iiif.enabled = false # iiif.search.url = ${solr.server}/${solr.multicorePrefix}ocr # iiif.search.plugin = org.dspace.app.iiif.service.WordHighlightSolrSearch # ocr.tesseract.path = + +######################################################### +### LOGIN MIUR configuration ### + +### ANCE SEARCH JOURNALS ENDPOINT +#ance.webservice.search.endpoint = https://webservice.cineca.it/ance-ws/services/riviste + +### ANCE REQUEST JOURNALS ENDPOINT +#ance.webservice.addjournal.endpoint = https://webservice.cineca.it/pubblicazioni + + +######################################################## + +dspace.task.executor = dspaceRunnableThreadExecutor + +####################################### +# ******* ORCHESTRATOR CONFIG ******* # +####################################### + +dspace.serviceid = +dspace.serviceid.security_token = + +aws.sqs.region = +aws.sqs.accessKey = +aws.sqs.secretAccessKey = + +aws.sqs.data_queue_uri = +aws.sqs.command_queue_uri = +aws.sqs.heartbeat_queue_uri = + +# List of scripts name that are required to be processed locally instead to be delegated to the orchestrator. +orchestrator.ignore-script = process-cleaner, item-export, bulk-item-export, cris-layout-tool, export-cris-layout-tool, deduplication-merge-items, bulk-access-control, export-schema + diff --git a/dspace/config/modules/authority.cfg b/dspace/config/modules/authority.cfg index 99224f7d0fbe..58228900dbf1 100644 --- a/dspace/config/modules/authority.cfg +++ b/dspace/config/modules/authority.cfg @@ -96,6 +96,12 @@ plugin.named.org.dspace.content.authority.ChoiceAuthority = \ org.dspace.content.authority.ZDBAuthority = ZDBAuthority,\ org.dspace.content.authority.SherpaAuthority = SherpaAuthority + +### Login MIUR +#for login MIUR addon please configure the following entry in the list above +#org.dspace.content.authority.ANCEAuthority = JournalAuthority + + cris.ItemAuthority.forceInternalName = true # AuthorStrictMatchAuthority configuration @@ -286,4 +292,5 @@ authority.controlled.dc.type = true choices.plugin.dc.type = ControlledVocabularyAuthority # DSpace-CRIS stores by default the authority of controlled vocabularies -vocabulary.plugin.authority.store = true \ No newline at end of file +vocabulary.plugin.authority.store = true +authority.controlled.cris.virtual.department = true diff --git a/dspace/config/modules/curate.cfg b/dspace/config/modules/curate.cfg index 0a160d9a8110..d9059173382b 100644 --- a/dspace/config/modules/curate.cfg +++ b/dspace/config/modules/curate.cfg @@ -39,6 +39,7 @@ plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.RegisterD #plugin.named.org.dspace.curate.CurationTask = org.dspace.app.ocr.OCRIndexerCurationTask = pushocr #plugin.named.org.dspace.curate.CurationTask = org.dspace.app.ocr.OCRIndexerCurationTask = pushocr.force #plugin.named.org.dspace.curate.CurationTask = org.dspace.app.ocr.OCRCleanCurationTask = ocrclean +#plugin.named.org.dspace.curate.CurationTask = org.dspace.app.ocr.migration.OCRMigrationCurationTask = ocrmigrate # add new tasks here (or in additional config files) ## task queue implementation diff --git a/dspace/config/modules/deduplication.cfg b/dspace/config/modules/deduplication.cfg index c2f42dc2902f..c5d2eaa4b29c 100644 --- a/dspace/config/modules/deduplication.cfg +++ b/dspace/config/modules/deduplication.cfg @@ -28,6 +28,9 @@ deduplication.tool.duplicatechecker.ignorewithdrawn = true # only reported section don't check submitter suggestion duplicate deduplication.tool.duplicatechecker.ignore.submitter.suggestion = true +# facet limit for finding number of deduplication sets under a signature +deduplication.facet-limit = 150 + #------------------------------------------------------------------# #------------DEDUPLICATION / DATAQUALITY CONFIGURATIONS------------# #------------------------------------------------------------------# @@ -36,4 +39,4 @@ deduplication.tool.duplicatechecker.ignore.submitter.suggestion = true # # #------------------------------------------------------------------# # metadata here listed will be excluded by merge tool logic -merge.excluded-metadata = dc.description.provenance +merge.excluded-metadata = dc.description.provenance, datacite.rights, dc.date.accessioned, dc.date.modified, dc.date.available, dspace.entity.type diff --git a/dspace/config/modules/orcid.cfg b/dspace/config/modules/orcid.cfg index 4bce1fe38f96..778a48123317 100644 --- a/dspace/config/modules/orcid.cfg +++ b/dspace/config/modules/orcid.cfg @@ -86,6 +86,33 @@ orcid.mapping.work.funding.external-id.value = dc.relation.grantno orcid.mapping.work.funding.external-id.entity-value = oairecerif.funding.identifier orcid.mapping.work.funding.url = crisfund.award.url +### Work (Product) mapping ### +# where is differs from publication mapping above +# see orcid-services.xml for properties being changed here! +# https://info.orcid.org/documentation/integration-and-api-faq/#easy-faq-2682 +# https://info.orcid.org/faq/what-contributor-information-should-i-include-when-adding-works-or-funding-items/ + +# aligned to default submittion form for "product" +orcid.mapping.work.product.contributors = dc.contributor.author::author + +# Additional Mapping to CRediT roles https://credit.niso.org/ possible +# The roles are not part of the current used orcid model and thus it is not possible to configure the NISO-roles + +orcid.mapping.work.product.type.converter = mapConverterDSpaceToOrcidProductType + +### Work (Patent) mapping ### +# where is differs from publication mapping above +# see orcid-services.xml for properties being changed here! +# https://info.orcid.org/documentation/integration-and-api-faq/#easy-faq-2682 +# https://info.orcid.org/faq/what-contributor-information-should-i-include-when-adding-works-or-funding-items/ +orcid.mapping.work.patent.contributors = dc.contributor.author::author + +orcid.mapping.work.patent.journal-title = dc.publisher + +orcid.mapping.work.patent.type.converter = mapConverterDSpaceToOrcidPatentType +orcid.mapping.work.patent.external-ids = $simple-handle::handle +orcid.mapping.work.patent.external-ids = dc.identifier.patentno::pat + ### Funding mapping ### orcid.mapping.funding.title = dc.title orcid.mapping.funding.type = dc.type diff --git a/dspace/config/registries/dspace-types.xml b/dspace/config/registries/dspace-types.xml index 861dc67a816a..a3006d8ae1a2 100644 --- a/dspace/config/registries/dspace-types.xml +++ b/dspace/config/registries/dspace-types.xml @@ -87,6 +87,20 @@ Stores the publication synchronization with ORCID preference chosen by the user + + dspace + orcid + sync-products + Stores the product synchronization with ORCID preference chosen by the user + + + + dspace + orcid + sync-patents + Stores the patent synchronization with ORCID preference chosen by the user + + dspace orcid diff --git a/dspace/config/registries/miur-types.xml b/dspace/config/registries/miur-types.xml new file mode 100644 index 000000000000..92fa63e44b4a --- /dev/null +++ b/dspace/config/registries/miur-types.xml @@ -0,0 +1,75 @@ + + + + DSpace Miur Types + + + + miur + http://dspace.org/miur + + + + miur + journal + startdate + + + + + miur + journal + enddate + + + + + miur + journal + type + + + + + miur + identifier + ance + + + + + miur + person + cf + + + + + miur + type + referee + + + + + miur + patent + relevance + + + + + miur + bitstream + synch + + + + + miur + journal + othertitle + + + + \ No newline at end of file diff --git a/dspace/config/spring/api/core-dao-services.xml b/dspace/config/spring/api/core-dao-services.xml index c1518454e832..f9d8513cdb3a 100644 --- a/dspace/config/spring/api/core-dao-services.xml +++ b/dspace/config/spring/api/core-dao-services.xml @@ -87,5 +87,6 @@ + diff --git a/dspace/config/spring/api/crosswalks.xml b/dspace/config/spring/api/crosswalks.xml index 9184a56482da..8d7bb2ae9109 100644 --- a/dspace/config/spring/api/crosswalks.xml +++ b/dspace/config/spring/api/crosswalks.xml @@ -554,7 +554,9 @@ - + +
- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -171,7 +229,19 @@ - + + + + + + + + + + + + + diff --git a/dspace/etc/conftool/cris-layout-configuration.xls b/dspace/etc/conftool/cris-layout-configuration.xls index d9c9ab9f0090..4f8e3acbf843 100644 Binary files a/dspace/etc/conftool/cris-layout-configuration.xls and b/dspace/etc/conftool/cris-layout-configuration.xls differ