diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml
index aae4b282b654..1c6879a5d701 100644
--- a/dspace-api/pom.xml
+++ b/dspace-api/pom.xml
@@ -12,7 +12,7 @@
org.dspace
dspace-parent
- cris-2023.02.00
+ cris-2023.02.02-SNAPSHOT
..
diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java
index eef8a859157d..0682082e03f8 100644
--- a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java
@@ -407,6 +407,13 @@ public Bitstream getBitstreamByName(Item item, String bundleName, String bitstre
return null;
}
+ @Override
+ public List getBitstreamByBundleName(Item item, String bundleName) throws SQLException {
+ return itemService.getBundles(item, bundleName).stream()
+ .flatMap(bundle -> bundle.getBitstreams().stream())
+ .collect(Collectors.toList());
+ }
+
@Override
public Bitstream getFirstBitstream(Item item, String bundleName) throws SQLException {
List bundles = itemService.getBundles(item, bundleName);
diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/METSDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/METSDisseminationCrosswalk.java
index b8a4a8aef390..e6156e78d295 100644
--- a/dspace-api/src/main/java/org/dspace/content/crosswalk/METSDisseminationCrosswalk.java
+++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/METSDisseminationCrosswalk.java
@@ -72,6 +72,16 @@ public class METSDisseminationCrosswalk
private static final String schemaLocation =
METS_NS.getURI() + " " + METS_XSD;
+ private String metsPackagerPlugin;
+
+ public METSDisseminationCrosswalk() {
+ this.metsPackagerPlugin = METS_PACKAGER_PLUGIN;
+ }
+
+ public METSDisseminationCrosswalk(String metsPackagerPlugin) {
+ this.metsPackagerPlugin = metsPackagerPlugin;
+ }
+
@Override
public Namespace[] getNamespaces() {
return (Namespace[]) ArrayUtils.clone(namespaces);
@@ -103,10 +113,10 @@ public Element disseminateElement(Context context, DSpaceObject dso)
PackageDisseminator dip = (PackageDisseminator)
CoreServiceFactory.getInstance().getPluginService()
- .getNamedPlugin(PackageDisseminator.class, METS_PACKAGER_PLUGIN);
+ .getNamedPlugin(PackageDisseminator.class, metsPackagerPlugin);
if (dip == null) {
throw new CrosswalkInternalException(
- "Cannot find a disseminate plugin for package=" + METS_PACKAGER_PLUGIN);
+ "Cannot find a disseminate plugin for package=" + metsPackagerPlugin);
}
try {
@@ -117,11 +127,16 @@ public Element disseminateElement(Context context, DSpaceObject dso)
// Create a temporary file to disseminate into
ConfigurationService configurationService
= DSpaceServicesFactory.getInstance().getConfigurationService();
- String tempDirectory = (configurationService.hasProperty("upload.temp.dir"))
+ String tempDirectoryPath = (configurationService.hasProperty("upload.temp.dir"))
? configurationService.getProperty("upload.temp.dir")
: System.getProperty("java.io.tmpdir");
- File tempFile = File.createTempFile("METSDissemination" + dso.hashCode(), null, new File(tempDirectory));
+ File tempDirectory = new File(tempDirectoryPath);
+ if (!tempDirectory.exists()) {
+ tempDirectory.mkdirs();
+ }
+
+ File tempFile = File.createTempFile("METSDissemination" + dso.hashCode(), null, tempDirectory);
tempFile.deleteOnExit();
// Disseminate METS to temp file
diff --git a/dspace-api/src/main/java/org/dspace/content/integration/crosswalks/ItemExportCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/integration/crosswalks/ItemExportCrosswalk.java
index 3a8b5a1524d1..dba686198e8a 100644
--- a/dspace-api/src/main/java/org/dspace/content/integration/crosswalks/ItemExportCrosswalk.java
+++ b/dspace-api/src/main/java/org/dspace/content/integration/crosswalks/ItemExportCrosswalk.java
@@ -11,6 +11,7 @@
import org.dspace.content.crosswalk.CrosswalkMode;
import org.dspace.content.crosswalk.StreamDisseminationCrosswalk;
+import org.dspace.core.Context;
/**
* Implementation of {@link StreamDisseminationCrosswalk} related to item
@@ -40,4 +41,8 @@ public default Optional getEntityType() {
public default CrosswalkMode getCrosswalkMode() {
return CrosswalkMode.SINGLE;
}
+
+ public default boolean isAuthorized(Context context) {
+ return true;
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/content/integration/crosswalks/METSStreamDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/integration/crosswalks/METSStreamDisseminationCrosswalk.java
new file mode 100644
index 000000000000..292a1e14f946
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/content/integration/crosswalks/METSStreamDisseminationCrosswalk.java
@@ -0,0 +1,63 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.content.integration.crosswalks;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.sql.SQLException;
+import javax.annotation.PostConstruct;
+
+import org.dspace.authorize.AuthorizeException;
+import org.dspace.content.DSpaceObject;
+import org.dspace.content.crosswalk.CrosswalkException;
+import org.dspace.content.crosswalk.METSDisseminationCrosswalk;
+import org.dspace.content.crosswalk.StreamDisseminationCrosswalk;
+import org.dspace.core.Context;
+import org.jdom2.Element;
+import org.jdom2.output.Format;
+import org.jdom2.output.XMLOutputter;
+
+/**
+ * Implementation of {@link StreamDisseminationCrosswalk} that produces a METS
+ * manifest for the DSpace item as a metadata description, using
+ * {@link METSDisseminationCrosswalk}.
+ *
+ * @author Luca Giamminonni (luca.giamminonni at 4science.it)
+ *
+ */
+public class METSStreamDisseminationCrosswalk implements StreamDisseminationCrosswalk {
+
+ private METSDisseminationCrosswalk metsDisseminationCrosswalk;
+
+ @PostConstruct
+ public void setup() {
+ metsDisseminationCrosswalk = new METSDisseminationCrosswalk("AIP");
+ }
+
+ @Override
+ public boolean canDisseminate(Context context, DSpaceObject dso) {
+ return metsDisseminationCrosswalk.canDisseminate(dso);
+ }
+
+ @Override
+ public void disseminate(Context context, DSpaceObject dso, OutputStream out)
+ throws CrosswalkException, IOException, SQLException, AuthorizeException {
+
+ Element element = metsDisseminationCrosswalk.disseminateElement(context, dso);
+
+ XMLOutputter xmlOutputter = new XMLOutputter(Format.getPrettyFormat());
+ xmlOutputter.output(element, out);
+
+ }
+
+ @Override
+ public String getMIMEType() {
+ return "application/xml";
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/content/integration/crosswalks/ReferCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/integration/crosswalks/ReferCrosswalk.java
index d54fef41ee68..519d9531cb71 100644
--- a/dspace-api/src/main/java/org/dspace/content/integration/crosswalks/ReferCrosswalk.java
+++ b/dspace-api/src/main/java/org/dspace/content/integration/crosswalks/ReferCrosswalk.java
@@ -58,6 +58,9 @@
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.discovery.configuration.DiscoveryConfigurationUtilsService;
+import org.dspace.eperson.EPerson;
+import org.dspace.eperson.Group;
+import org.dspace.eperson.service.GroupService;
import org.dspace.services.ConfigurationService;
import org.dspace.util.UUIDUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -94,6 +97,9 @@ public class ReferCrosswalk implements ItemExportCrosswalk {
@Autowired
private MetadataSecurityService metadataSecurityService;
+ @Autowired
+ private GroupService groupService;
+
private Converter converter;
private Consumer> linesPostProcessor;
@@ -116,6 +122,8 @@ public class ReferCrosswalk implements ItemExportCrosswalk {
private CrosswalkMode crosswalkMode;
+ private List allowedGroups;
+
@PostConstruct
private void postConstruct() throws IOException {
String parent = configurationService.getProperty("dspace.dir") + File.separator + "config" + File.separator;
@@ -128,6 +136,21 @@ private void postConstruct() throws IOException {
}
}
+ @Override
+ public boolean isAuthorized(Context context) {
+ if (CollectionUtils.isEmpty(allowedGroups)) {
+ return true;
+ }
+
+ EPerson ePerson = context.getCurrentUser();
+ if (ePerson == null) {
+ return allowedGroups.contains(Group.ANONYMOUS);
+ }
+
+ return allowedGroups.stream()
+ .anyMatch(groupName -> isMemberOfGroupNamed(context, ePerson, groupName));
+ }
+
@Override
public void disseminate(Context context, DSpaceObject dso, OutputStream out)
throws CrosswalkException, IOException, SQLException, AuthorizeException {
@@ -136,6 +159,10 @@ public void disseminate(Context context, DSpaceObject dso, OutputStream out)
throw new CrosswalkObjectNotSupported("Can only crosswalk an Item with the configured type: " + entityType);
}
+ if (!isAuthorized(context)) {
+ throw new AuthorizeException("The current user is not allowed to perform a zip item export");
+ }
+
List lines = getItemLines(context, dso, true);
if (linesPostProcessor != null) {
@@ -154,6 +181,10 @@ public void disseminate(Context context, Iterator extends DSpaceObject> dsoIte
throw new UnsupportedOperationException("No template defined for multiple items");
}
+ if (!isAuthorized(context)) {
+ throw new AuthorizeException("The current user is not allowed to perform a zip item export");
+ }
+
List lines = new ArrayList();
for (TemplateLine line : multipleItemsTemplateLines) {
@@ -466,6 +497,15 @@ private boolean hasExpectedEntityType(Item item) {
return Objects.equals(itemEntityType, entityType);
}
+ private boolean isMemberOfGroupNamed(Context context, EPerson ePerson, String groupName) {
+ try {
+ Group group = groupService.findByName(context, groupName);
+ return groupService.isMember(context, ePerson, group);
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
public void setConverter(Converter converter) {
this.converter = converter;
}
@@ -525,4 +565,12 @@ public void setPubliclyReadable(boolean isPubliclyReadable) {
this.publiclyReadable = isPubliclyReadable;
}
+ public List getAllowedGroups() {
+ return allowedGroups;
+ }
+
+ public void setAllowedGroups(List allowedGroups) {
+ this.allowedGroups = allowedGroups;
+ }
+
}
diff --git a/dspace-api/src/main/java/org/dspace/content/integration/crosswalks/ZipItemExportCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/integration/crosswalks/ZipItemExportCrosswalk.java
new file mode 100644
index 000000000000..2096fa037273
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/content/integration/crosswalks/ZipItemExportCrosswalk.java
@@ -0,0 +1,325 @@
+/**
+ * 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;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.dspace.authorize.AuthorizeException;
+import org.dspace.content.Bitstream;
+import org.dspace.content.DSpaceObject;
+import org.dspace.content.Item;
+import org.dspace.content.crosswalk.CrosswalkException;
+import org.dspace.content.crosswalk.CrosswalkMode;
+import org.dspace.content.crosswalk.CrosswalkObjectNotSupported;
+import org.dspace.content.crosswalk.StreamDisseminationCrosswalk;
+import org.dspace.content.service.BitstreamService;
+import org.dspace.content.service.ItemService;
+import org.dspace.core.Constants;
+import org.dspace.core.Context;
+import org.dspace.core.exception.SQLRuntimeException;
+import org.dspace.eperson.EPerson;
+import org.dspace.eperson.Group;
+import org.dspace.eperson.service.GroupService;
+import org.dspace.storage.bitstore.service.BitstreamStorageService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.Assert;
+
+/**
+ * Implementation of {@link ItemExportCrosswalk} that export all the given items
+ * creating a zip.
+ *
+ * @author Luca Giamminonni (luca.giamminonni at 4science.it)
+ *
+ */
+public class ZipItemExportCrosswalk implements ItemExportCrosswalk {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ZipItemExportCrosswalk.class);
+
+ @Autowired
+ private ItemService itemService;
+
+ @Autowired
+ private BitstreamService bitstreamService;
+
+ @Autowired
+ private BitstreamStorageService bitstreamStorageService;
+
+ @Autowired
+ private GroupService groupService;
+
+ private String zipName = "items.zip";
+
+ private String entityType;
+
+ private String bitstreamBundle = "ORIGINAL";
+
+ private String metadataFileName;
+
+ private StreamDisseminationCrosswalk crosswalk;
+
+ private CrosswalkMode crosswalkMode = CrosswalkMode.MULTIPLE;
+
+ private List allowedGroups;
+
+ @Override
+ public boolean isAuthorized(Context context) {
+ if (CollectionUtils.isEmpty(allowedGroups)) {
+ return true;
+ }
+
+ EPerson ePerson = context.getCurrentUser();
+ if (ePerson == null) {
+ return allowedGroups.contains(Group.ANONYMOUS);
+ }
+
+ return allowedGroups.stream()
+ .anyMatch(groupName -> isMemberOfGroupNamed(context, ePerson, groupName));
+ }
+
+ @Override
+ public boolean canDisseminate(Context context, DSpaceObject dso) {
+ return dso.getType() == Constants.ITEM && hasExpectedEntityType((Item) dso);
+ }
+
+ @Override
+ public void disseminate(Context context, DSpaceObject dso, OutputStream out)
+ throws CrosswalkException, IOException, SQLException, AuthorizeException {
+ this.disseminate(context, Arrays.asList(dso).iterator(), out);
+ }
+
+ @Override
+ public void disseminate(Context context, Iterator extends DSpaceObject> dsoIterator, OutputStream out)
+ throws CrosswalkException, IOException, SQLException, AuthorizeException {
+
+ Assert.notNull(metadataFileName, "The name of the metadata file is required to perform a bulk item export");
+ Assert.notNull(crosswalk, "An instance of DisseminationCrosswalk is required to perform a bulk item export");
+ Assert.notNull(zipName, "The name of the zip to be generated is required to perform a bulk item export");
+
+ if (!isAuthorized(context)) {
+ throw new AuthorizeException("The current user is not allowed to perform a zip item export");
+ }
+
+ createZip(context, dsoIterator, out);
+
+ }
+
+ private void createZip(Context context, Iterator extends DSpaceObject> dsoIterator, OutputStream out)
+ throws CrosswalkObjectNotSupported, IOException {
+
+ try (ZipOutputStream zos = new ZipOutputStream(out)) {
+
+ while (dsoIterator.hasNext()) {
+
+ DSpaceObject dso = dsoIterator.next();
+ if (!canDisseminate(context, dso)) {
+ throw new CrosswalkObjectNotSupported(
+ "Can only crosswalk an Item with the configured type: " + entityType);
+ }
+
+ try {
+ createFolder(context, (Item) dso, zos);
+ } catch (Exception ex) {
+ LOGGER.error("An error occurs creating folder for item " + dso.getID(), ex);
+ }
+
+ }
+
+ }
+
+ }
+
+ private void createFolder(Context context, Item item, ZipOutputStream zos) throws IOException {
+
+ createMetadataEntry(context, item, zos);
+
+ List bitstreams = getBitstreamToExport(item);
+ for (Bitstream bitstream : bitstreams) {
+ try {
+ addBitstreamEntry(context, item, bitstream, zos);
+ } catch (Exception ex) {
+ LOGGER.error("An error occurs adding bitstream " + bitstream.getID()
+ + " to the folder of item " + item.getID(), ex);
+ }
+ }
+
+ }
+
+ private void createMetadataEntry(Context context, Item item, ZipOutputStream zos) throws IOException {
+ ZipEntry metadataEntry = new ZipEntry(getFolderName(item) + "/" + getMetadataFileName());
+ zos.putNextEntry(metadataEntry);
+ zos.write(getMetadataFileNameContent(context, item));
+ zos.closeEntry();
+ }
+
+ private byte[] getMetadataFileNameContent(Context context, Item item) {
+ try {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ crosswalk.disseminate(context, item, out);
+ return out.toByteArray();
+ } catch (CrosswalkException | IOException | SQLException | AuthorizeException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private List getBitstreamToExport(Item item) {
+ try {
+ return bitstreamService.getBitstreamByBundleName(item, bitstreamBundle);
+ } catch (SQLException e) {
+ throw new SQLRuntimeException(e);
+ }
+ }
+
+ private void addBitstreamEntry(Context context, Item item, Bitstream bitstream, ZipOutputStream zos)
+ throws IOException {
+
+ InputStream bitstreamContent = retrieveContent(context, bitstream);
+
+ ZipEntry bitstreamEntry = new ZipEntry(getFolderName(item) + "/" + getBitstreamFileName(context, bitstream));
+ zos.putNextEntry(bitstreamEntry);
+
+ try {
+ writeBitstreamContent(bitstreamContent, zos);
+ } finally {
+ zos.closeEntry();
+ }
+
+ }
+
+ private void writeBitstreamContent(InputStream content, ZipOutputStream zos) throws IOException {
+ byte[] bytes = new byte[1024];
+ int length;
+ while ((length = content.read(bytes)) >= 0) {
+ zos.write(bytes, 0, length);
+ }
+ }
+
+ private String getBitstreamFileName(Context context, Bitstream bitstream) {
+ String name = "bitstream_" + bitstream.getID().toString();
+ return getBitstreamExtension(context, bitstream)
+ .map(extension -> name + "." + extension)
+ .orElse(name);
+ }
+
+ private Optional getBitstreamExtension(Context context, Bitstream bitstream) {
+ try {
+ return bitstream.getFormat(context).getExtensions().stream().findFirst();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private InputStream retrieveContent(Context context, Bitstream bitstream) {
+ try {
+ return bitstreamStorageService.retrieve(context, bitstream);
+ } catch (SQLException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private String getMetadataFileName() {
+ return metadataFileName;
+ }
+
+ private String getFolderName(Item item) {
+ return item.getID().toString();
+ }
+
+ private boolean isMemberOfGroupNamed(Context context, EPerson ePerson, String groupName) {
+ try {
+ Group group = groupService.findByName(context, groupName);
+ return groupService.isMember(context, ePerson, group);
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String getMIMEType() {
+ return "application/octet-stream";
+ }
+
+ public void setCrosswalkMode(CrosswalkMode crosswalkMode) {
+ this.crosswalkMode = crosswalkMode;
+ }
+
+ @Override
+ public CrosswalkMode getCrosswalkMode() {
+ return Optional.ofNullable(this.crosswalkMode).orElse(CrosswalkMode.MULTIPLE);
+ }
+
+ private boolean hasExpectedEntityType(Item item) {
+ if (StringUtils.isBlank(entityType)) {
+ return true;
+ }
+ return entityType.equals(itemService.getEntityType(item));
+ }
+
+ @Override
+ public String getFileName() {
+ return getZipName();
+ }
+
+ public String getZipName() {
+ return zipName;
+ }
+
+ public void setZipName(String zipName) {
+ this.zipName = zipName;
+ }
+
+ public Optional getEntityType() {
+ return Optional.ofNullable(entityType);
+ }
+
+ public void setEntityType(String entityType) {
+ this.entityType = entityType;
+ }
+
+ public StreamDisseminationCrosswalk getCrosswalk() {
+ return crosswalk;
+ }
+
+ public void setCrosswalk(StreamDisseminationCrosswalk crosswalk) {
+ this.crosswalk = crosswalk;
+ }
+
+ public String getBitstreamBundle() {
+ return bitstreamBundle;
+ }
+
+ public void setBitstreamBundle(String bitstreamBundle) {
+ this.bitstreamBundle = bitstreamBundle;
+ }
+
+ public void setMetadataFileName(String metadataFileName) {
+ this.metadataFileName = metadataFileName;
+ }
+
+ public List getAllowedGroups() {
+ return allowedGroups;
+ }
+
+ public void setAllowedGroups(List allowedGroups) {
+ this.allowedGroups = allowedGroups;
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/content/integration/crosswalks/service/ItemExportFormatServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/integration/crosswalks/service/ItemExportFormatServiceImpl.java
index 4d33ba35c5e8..5745ec3e8ce8 100644
--- a/dspace-api/src/main/java/org/dspace/content/integration/crosswalks/service/ItemExportFormatServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/content/integration/crosswalks/service/ItemExportFormatServiceImpl.java
@@ -45,6 +45,7 @@ public ItemExportFormat get(Context context, String id) {
public List getAll(Context context) {
return this.streamDissiminatorCrosswalkMapper.getAllItemExportCrosswalks().entrySet().stream()
+ .filter(entry -> entry.getValue().isAuthorized(context))
.map(entry -> buildItemExportFormat(entry.getKey(), entry.getValue()))
.collect(Collectors.toList());
@@ -58,6 +59,7 @@ public List byEntityTypeAndMolteplicity(Context context, Strin
.entrySet().stream()
.filter(entry -> hasSameMolteplicity(entry.getValue(), molteplicity))
.filter(entry -> hasSameEntityType(entry.getValue(), entityType))
+ .filter(entry -> entry.getValue().isAuthorized(context))
.map(entry -> buildItemExportFormat(entry.getKey(), entry.getValue()))
.collect(Collectors.toList());
diff --git a/dspace-api/src/main/java/org/dspace/content/service/BitstreamService.java b/dspace-api/src/main/java/org/dspace/content/service/BitstreamService.java
index 85a4fd140e9a..fa1cbc38beae 100644
--- a/dspace-api/src/main/java/org/dspace/content/service/BitstreamService.java
+++ b/dspace-api/src/main/java/org/dspace/content/service/BitstreamService.java
@@ -210,6 +210,8 @@ public InputStream retrieve(Context context, Bitstream bitstream)
public Bitstream getBitstreamByName(Item item, String bundleName, String bitstreamName) throws SQLException;
+ List getBitstreamByBundleName(Item item, String bundleName) throws SQLException;
+
public Bitstream getFirstBitstream(Item item, String bundleName) throws SQLException;
public Bitstream getThumbnail(Context context, Bitstream bitstream) throws SQLException;
diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceBestMatchIndexingPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceBestMatchIndexingPlugin.java
index 39130e9224d2..a1830a3931c7 100644
--- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceBestMatchIndexingPlugin.java
+++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceBestMatchIndexingPlugin.java
@@ -68,7 +68,7 @@ protected void addIndexValueForPersonItem(Item item, SolrInputDocument document)
String lastName = getMetadataValue(item, LASTNAME_FIELD);
List fullNames = getMetadataValues(item, FULLNAME_FIELDS);
- getAllNameVariants(firstName, lastName, fullNames)
+ getAllNameVariants(firstName, lastName, fullNames, item.getID().toString())
.forEach(variant -> addIndexValue(document, variant));
}
diff --git a/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidAffiliationFactory.java b/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidAffiliationFactory.java
index d74f05bcaf50..5b325a44a37a 100644
--- a/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidAffiliationFactory.java
+++ b/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidAffiliationFactory.java
@@ -19,6 +19,7 @@
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
@@ -56,9 +57,9 @@ public OrcidAffiliationFactory(OrcidProfileSectionType sectionType, OrcidProfile
@Override
public List getMetadataFields() {
- return List.of(organizationField, roleField, startDateField, endDateField).stream()
- .filter(StringUtils::isNotBlank)
- .collect(Collectors.toList());
+ return Stream.of(organizationField, roleField, startDateField, endDateField)
+ .filter(StringUtils::isNotBlank)
+ .collect(Collectors.toList());
}
@Override
@@ -80,14 +81,17 @@ public Object create(Context context, List metadataValues) {
orcidCommonObjectFactory.createFuzzyDate(endDate).ifPresent(affiliation::setEndDate);
affiliation.setRoleTitle(isUnprocessableValue(role) ? null : role.getValue());
- orcidCommonObjectFactory.createOrganization(context, organization).ifPresent(affiliation::setOrganization);
+ orcidCommonObjectFactory.createOrganization(context, organization).ifPresent(org -> {
+ affiliation.setOrganization(org);
+ affiliation.setDepartmentName(org.getName());
+ });
return affiliation;
}
@Override
public List getMetadataSignatures(Context context, Item item) {
- List signatures = new ArrayList();
+ List signatures = new ArrayList<>();
Map> metadataGroups = getMetadataGroups(item);
int groupSize = metadataGroups.getOrDefault(organizationField, Collections.emptyList()).size();
@@ -95,7 +99,7 @@ public List getMetadataSignatures(Context context, Item item) {
List metadataValues = getMetadataValueByPlace(metadataGroups, currentGroupIndex);
//only "visible" metadatavalues within this group
metadataValues = metadataValues.stream()
- .filter(metadataValue -> isAllowedMetadataByVisibility(metadataValue))
+ .filter(this::isAllowedMetadataByVisibility)
.collect(Collectors.toList());
if (!metadataValues.isEmpty()) {
signatures.add(metadataSignatureGenerator.generate(context, metadataValues));
@@ -168,7 +172,7 @@ private Map> getMetadataGroups(Item item) {
}
private List getMetadataValueByPlace(Map> metadataGroups, int place) {
- List metadataValues = new ArrayList();
+ List metadataValues = new ArrayList<>();
for (String metadataField : metadataGroups.keySet()) {
List nestedMetadataValues = metadataGroups.get(metadataField);
if (nestedMetadataValues.size() > place) {
diff --git a/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidCommonObjectFactoryImpl.java b/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidCommonObjectFactoryImpl.java
index 48cda3b3da20..8d92e72b80c0 100644
--- a/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidCommonObjectFactoryImpl.java
+++ b/dspace-api/src/main/java/org/dspace/orcid/model/factory/impl/OrcidCommonObjectFactoryImpl.java
@@ -121,7 +121,9 @@ public Optional createOrganization(Context context, MetadataValue
Item organizationItem = findRelatedItem(context, metadataValue);
if (organizationItem != null) {
organization.setAddress(createOrganizationAddress(organizationItem));
- organization.setDisambiguatedOrganization(createDisambiguatedOrganization(organizationItem));
+ organization.setDisambiguatedOrganization(
+ createDisambiguatedOrganization(context, organizationItem)
+ );
}
return of(organization);
@@ -156,7 +158,7 @@ public Optional createFundingContributor(Context context, Me
FundingContributor contributor = new FundingContributor();
contributor.setCreditName(new CreditName(metadataValue.getValue()));
- contributor.setContributorAttributes(getFundingContributorAttributes(metadataValue, role));
+ contributor.setContributorAttributes(getFundingContributorAttributes(role));
Item authorItem = findItem(context, UUIDUtils.fromString(metadataValue.getAuthority()));
if (authorItem != null) {
@@ -190,7 +192,7 @@ public Optional createCountry(Context context, MetadataValue metadataVa
throw new OrcidValidationException(OrcidValidationError.INVALID_COUNTRY);
}
- return country.map(isoCountry -> new Country(isoCountry));
+ return country.map(Country::new);
}
private ContributorAttributes getContributorAttributes(MetadataValue metadataValue, ContributorRole role) {
@@ -211,8 +213,7 @@ private OrganizationAddress createOrganizationAddress(Item organizationItem) {
return address;
}
- private FundingContributorAttributes getFundingContributorAttributes(MetadataValue metadataValue,
- FundingContributorRole role) {
+ private FundingContributorAttributes getFundingContributorAttributes(FundingContributorRole role) {
FundingContributorAttributes attributes = new FundingContributorAttributes();
attributes.setContributorRole(role != null ? role.value() : null);
return attributes;
@@ -237,11 +238,23 @@ private DisambiguatedOrganization createDisambiguatedOrganization(Item organizat
return null;
}
+ private DisambiguatedOrganization createDisambiguatedOrganization(Context context, Item organizationItem) {
+ DisambiguatedOrganization disambiguatedOrganization = createDisambiguatedOrganization(organizationItem);
+ Item parentOrganization = findParentOrganization(context, organizationItem);
+
+ while (disambiguatedOrganization == null && parentOrganization != null) {
+ disambiguatedOrganization = createDisambiguatedOrganization(parentOrganization);
+ parentOrganization = findParentOrganization(context, parentOrganization);
+ }
+
+ return disambiguatedOrganization;
+ }
+
private Optional convertToIso3166Country(String countryValue) {
return ofNullable(countryValue)
.map(value -> countryConverter != null ? countryConverter.getValue(value) : value)
.filter(value -> isValidEnum(Iso3166Country.class, value))
- .map(value -> Iso3166Country.fromValue(value));
+ .map(Iso3166Country::fromValue);
}
private boolean isUnprocessableValue(MetadataValue value) {
@@ -249,6 +262,19 @@ private boolean isUnprocessableValue(MetadataValue value) {
|| value.getValue().equals(PLACEHOLDER_PARENT_METADATA_VALUE);
}
+ private Item findParentOrganization(Context context, Item item) {
+ try {
+ Optional metadataValue =
+ itemService.getMetadataByMetadataString(item, "organization.parentOrganization")
+ .stream().findFirst();
+ return metadataValue.isPresent()
+ ? itemService.find(context, UUIDUtils.fromString(metadataValue.get().getAuthority()))
+ : null;
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
private Item findRelatedItem(Context context, MetadataValue metadataValue) {
try {
return itemService.find(context, UUIDUtils.fromString(metadataValue.getAuthority()));
diff --git a/dspace-api/src/main/java/org/dspace/util/PersonNameUtil.java b/dspace-api/src/main/java/org/dspace/util/PersonNameUtil.java
index 0e88a0a9cdf5..cea02c76990b 100644
--- a/dspace-api/src/main/java/org/dspace/util/PersonNameUtil.java
+++ b/dspace-api/src/main/java/org/dspace/util/PersonNameUtil.java
@@ -16,6 +16,7 @@
import org.apache.commons.collections4.iterators.PermutationIterator;
import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.Logger;
/**
* Utility class that handle person names.
@@ -24,6 +25,7 @@
*
*/
public final class PersonNameUtil {
+ private static Logger log = org.apache.logging.log4j.LogManager.getLogger(PersonNameUtil.class);
private PersonNameUtil() {
@@ -35,12 +37,14 @@ private PersonNameUtil() {
* @param firstName the first name
* @param lastName the last name
* @param fullNames the full names
+ * @param uuid the uuid
* @return all the variants of the given names
*/
- public static Set getAllNameVariants(String firstName, String lastName, List fullNames) {
+ public static Set getAllNameVariants(String firstName, String lastName, List fullNames,
+ String uuid) {
Set variants = new HashSet();
variants.addAll(getNameVariants(firstName, lastName));
- variants.addAll(getNameVariants(fullNames));
+ variants.addAll(getNameVariants(fullNames, uuid));
return variants;
}
@@ -95,24 +99,30 @@ private static List getNameVariants(String[] firstNames, String lastName
return variants;
}
- private static List getNameVariants(List fullNames) {
+ private static List getNameVariants(List fullNames, String uuid) {
return fullNames.stream()
.filter(Objects::nonNull)
.map(name -> removeComma(name))
.distinct()
- .flatMap(name -> getAllNamePermutations(name).stream())
+ .flatMap(name -> getAllNamePermutations(name, uuid).stream())
.distinct()
.collect(Collectors.toList());
}
- private static List getAllNamePermutations(String name) {
+ private static List getAllNamePermutations(String name, String uuid) {
List namePermutations = new ArrayList();
- PermutationIterator permutationIterator = new PermutationIterator(List.of(name.split(" ")));
+ List names = List.of(name.split(" "));
+ if (names.size() < 5) {
+ PermutationIterator permutationIterator = new PermutationIterator(names);
- while (permutationIterator.hasNext()) {
- namePermutations.add(String.join(" ", permutationIterator.next()));
+ while (permutationIterator.hasNext()) {
+ namePermutations.add(String.join(" ", permutationIterator.next()));
+ }
+ } else {
+ log.warn(String.format("Cannot retrieve variants on the Person with UUID %s because the name is too long",
+ uuid));
}
return namePermutations;
diff --git a/dspace-api/src/test/java/org/dspace/content/integration/crosswalks/ZipItemExportCrosswalkIT.java b/dspace-api/src/test/java/org/dspace/content/integration/crosswalks/ZipItemExportCrosswalkIT.java
new file mode 100644
index 000000000000..e824fef5a9b1
--- /dev/null
+++ b/dspace-api/src/test/java/org/dspace/content/integration/crosswalks/ZipItemExportCrosswalkIT.java
@@ -0,0 +1,256 @@
+/**
+ * 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;
+
+import static org.dspace.builder.CollectionBuilder.createCollection;
+import static org.dspace.builder.CommunityBuilder.createCommunity;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.sql.SQLException;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.apache.commons.collections.IteratorUtils;
+import org.apache.commons.io.IOUtils;
+import org.dspace.AbstractIntegrationTestWithDatabase;
+import org.dspace.authorize.AuthorizeException;
+import org.dspace.builder.BitstreamBuilder;
+import org.dspace.builder.EPersonBuilder;
+import org.dspace.builder.GroupBuilder;
+import org.dspace.builder.ItemBuilder;
+import org.dspace.content.Bitstream;
+import org.dspace.content.Collection;
+import org.dspace.content.Community;
+import org.dspace.content.Item;
+import org.dspace.eperson.EPerson;
+import org.dspace.eperson.Group;
+import org.dspace.utils.DSpace;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ZipItemExportCrosswalkIT extends AbstractIntegrationTestWithDatabase {
+
+ private ZipItemExportCrosswalk zipItemExportCrosswalk;
+
+ private Community community;
+
+ private Collection collection;
+
+ @Before
+ public void setup() throws SQLException, AuthorizeException {
+
+ zipItemExportCrosswalk = new DSpace().getServiceManager()
+ .getServicesByType(ZipItemExportCrosswalk.class).get(0);
+
+ context.turnOffAuthorisationSystem();
+ community = createCommunity(context).build();
+ collection = createCollection(context, community).build();
+ context.restoreAuthSystemState();
+
+ }
+
+ @Test
+ public void testItemsExportWithAdmin() throws Exception {
+
+ context.turnOffAuthorisationSystem();
+
+ Item item1 = createItem("Test Item 1", "2022-01-01", "Luca Giamminonni");
+ Item item2 = createItem("Test Item 2", "2022-03-01", "Walter White");
+ Item item3 = createItem("Test Item 3", "2020-01-01", "Andrea Bollini");
+
+ Bitstream bitstream1 = createBitstream(item1, "test.txt", "This is a test");
+ Bitstream bitstream2 = createBitstream(item3, "test.pdf", "Last test", "6 months");
+
+ String expectedEmbargo = LocalDate.now().plus(6, ChronoUnit.MONTHS).format(DateTimeFormatter.ISO_DATE);
+
+ context.restoreAuthSystemState();
+
+ context.setCurrentUser(admin);
+
+ File tempZip = File.createTempFile("test", "zip");
+ tempZip.deleteOnExit();
+
+ try (FileOutputStream fos = new FileOutputStream(tempZip)) {
+ zipItemExportCrosswalk.disseminate(context, List.of(item1, item2, item3).iterator(), fos);
+ }
+
+ try (ZipFile zipFile = new ZipFile(tempZip)) {
+
+ ZipEntry zipEntry = zipFile.getEntry(item1.getID().toString() + "/mets.xml");
+ assertThat(zipEntry, notNullValue());
+
+ String metsContent = getZipEntryContent(zipFile, zipEntry);
+
+ assertThat(metsContent, containsString(
+ "2022-01-01"));
+ assertThat(metsContent,
+ containsString("Test Item 1"));
+ assertThat(metsContent, containsString("Luca Giamminonni"));
+ assertThat(metsContent,
+ containsString("test@email.com"));
+ assertThat(metsContent,
+ containsString("test.txt"));
+
+ zipEntry = zipFile.getEntry(item1.getID().toString() + "/bitstream_" + bitstream1.getID().toString());
+ assertThat(zipEntry, notNullValue());
+ assertThat(getZipEntryContent(zipFile, zipEntry), is("This is a test"));
+
+ zipEntry = zipFile.getEntry(item2.getID().toString() + "/mets.xml");
+ assertThat(zipEntry, notNullValue());
+
+ metsContent = getZipEntryContent(zipFile, zipEntry);
+
+ assertThat(metsContent, containsString(
+ "2022-03-01"));
+ assertThat(metsContent,
+ containsString("Test Item 2"));
+ assertThat(metsContent, containsString("Walter White"));
+ assertThat(metsContent,
+ containsString("test@email.com"));
+
+ zipEntry = zipFile.getEntry(item3.getID().toString() + "/mets.xml");
+ assertThat(zipEntry, notNullValue());
+
+ metsContent = getZipEntryContent(zipFile, zipEntry);
+
+ assertThat(metsContent, containsString(
+ "2020-01-01"));
+ assertThat(metsContent,
+ containsString("Test Item 3"));
+ assertThat(metsContent, containsString("Andrea Bollini"));
+ assertThat(metsContent,
+ containsString("test@email.com"));
+ assertThat(metsContent, containsString(""));
+ assertThat(metsContent,
+ containsString("test.pdf"));
+
+ zipEntry = zipFile.getEntry(item3.getID().toString() + "/bitstream_" + bitstream2.getID().toString());
+ assertThat(zipEntry, notNullValue());
+ assertThat(getZipEntryContent(zipFile, zipEntry), is("Last test"));
+
+ assertThat(getAllEntries(zipFile), hasSize(5));
+
+ }
+
+ }
+
+ @Test
+ public void testItemsExportWithCurators() throws Exception {
+
+ context.turnOffAuthorisationSystem();
+
+ Group curators = GroupBuilder.createGroup(context)
+ .withName("Curators")
+ .build();
+
+ EPerson user = EPersonBuilder.createEPerson(context)
+ .withEmail("user@test.com")
+ .withGroupMembership(curators)
+ .build();
+
+ Item item1 = createItem("Test Item 1", "2022-01-01", "Luca Giamminonni");
+ Item item2 = createItem("Test Item 2", "2022-03-01", "Walter White");
+ Item item3 = createItem("Test Item 3", "2020-01-01", "Andrea Bollini");
+
+ context.restoreAuthSystemState();
+
+ context.setCurrentUser(user);
+
+ File tempZip = File.createTempFile("test", "zip");
+ tempZip.deleteOnExit();
+
+ try (FileOutputStream fos = new FileOutputStream(tempZip)) {
+ zipItemExportCrosswalk.disseminate(context, List.of(item1, item2, item3).iterator(), fos);
+ }
+
+ try (ZipFile zipFile = new ZipFile(tempZip)) {
+ assertThat(getAllEntries(zipFile), hasSize(3));
+ }
+
+ }
+
+ @Test
+ public void testItemsExportWithNotAuthorizedUser() throws Exception {
+
+ context.turnOffAuthorisationSystem();
+
+ Item item1 = createItem("Test Item 1", "2022-01-01", "Luca Giamminonni");
+ Item item2 = createItem("Test Item 2", "2022-03-01", "Walter White");
+ Item item3 = createItem("Test Item 3", "2020-01-01", "Andrea Bollini");
+
+ context.restoreAuthSystemState();
+
+ context.setCurrentUser(eperson);
+
+ File tempZip = File.createTempFile("test", "zip");
+ tempZip.deleteOnExit();
+
+ try (FileOutputStream fos = new FileOutputStream(tempZip)) {
+
+ AuthorizeException authorizeException = Assert.assertThrows(AuthorizeException.class,
+ () -> zipItemExportCrosswalk.disseminate(context, List.of(item1, item2, item3).iterator(), fos));
+
+ assertThat(authorizeException.getMessage(),
+ is("The current user is not allowed to perform a zip item export"));
+ }
+
+ }
+
+ private Item createItem(String title, String issueDate, String author) {
+ return ItemBuilder.createItem(context, collection)
+ .withTitle(title)
+ .withIssueDate(issueDate)
+ .withAuthor(author)
+ .build();
+ }
+
+ private Bitstream createBitstream(Item item, String name, String content) throws Exception {
+ return BitstreamBuilder.createBitstream(context, item, getInputStream(content))
+ .withName(name)
+ .build();
+ }
+
+ private Bitstream createBitstream(Item item, String name, String content, String embargoPeriod) throws Exception {
+ return BitstreamBuilder.createBitstream(context, item, getInputStream(content))
+ .withName(name)
+ .withEmbargoPeriod(embargoPeriod)
+ .build();
+ }
+
+ private String getZipEntryContent(ZipFile zipFile, ZipEntry zipEntry) throws IOException {
+ return IOUtils.toString(zipFile.getInputStream(zipEntry), StandardCharsets.UTF_8);
+ }
+
+ private InputStream getInputStream(String str) {
+ return IOUtils.toInputStream(str, StandardCharsets.UTF_8);
+ }
+
+ @SuppressWarnings("unchecked")
+ private List getAllEntries(ZipFile zipFile) {
+ return IteratorUtils.toList(zipFile.entries().asIterator());
+ }
+
+}
diff --git a/dspace-api/src/test/java/org/dspace/orcid/service/OrcidProfileSectionFactoryServiceIT.java b/dspace-api/src/test/java/org/dspace/orcid/service/OrcidProfileSectionFactoryServiceIT.java
index 4d2e85a5b99e..60122ba5dbde 100644
--- a/dspace-api/src/test/java/org/dspace/orcid/service/OrcidProfileSectionFactoryServiceIT.java
+++ b/dspace-api/src/test/java/org/dspace/orcid/service/OrcidProfileSectionFactoryServiceIT.java
@@ -242,6 +242,184 @@ public void testFullEmploymentCreation() {
}
+ @Test
+ public void testDisambiguationFromOrgUnitHierarchyOnEmploymentCreation() {
+
+ context.turnOffAuthorisationSystem();
+
+ Item orgUnitWithRinId = ItemBuilder.createItem(context, orgUnits)
+ .withTitle("4Science with rin")
+ .withOrgUnitCountry("IT")
+ .withOrgUnitLocality("Milan")
+ .withOrgUnitRinggoldIdentifier("12345")
+ .build();
+
+ Item orgUnit = ItemBuilder.createItem(context, orgUnits)
+ .withTitle("4Science")
+ .withOrgUnitCountry("IT")
+ .withOrgUnitLocality("Milan")
+ .withParentOrganization("4Science with rin", orgUnitWithRinId.getID().toString())
+ .build();
+
+ Item item = ItemBuilder.createItem(context, collection)
+ .withTitle("Test profile")
+ .withPersonAffiliation("4Science", orgUnit.getID().toString())
+ .withPersonAffiliationStartDate("2020-02")
+ .withPersonAffiliationEndDate(PLACEHOLDER_PARENT_METADATA_VALUE)
+ .withPersonAffiliationRole("Researcher")
+ .build();
+
+ context.restoreAuthSystemState();
+
+ List values = new ArrayList<>();
+ values.add(getMetadata(item, "oairecerif.person.affiliation", 0));
+ values.add(getMetadata(item, "oairecerif.affiliation.startDate", 0));
+ values.add(getMetadata(item, "oairecerif.affiliation.endDate", 0));
+ values.add(getMetadata(item, "oairecerif.affiliation.role", 0));
+
+ Object firstOrcidObject = profileSectionFactoryService.createOrcidObject(context, values, AFFILIATION);
+ assertThat(firstOrcidObject, instanceOf(Employment.class));
+ Employment qualification = (Employment) firstOrcidObject;
+ assertThat(qualification.getStartDate(), notNullValue());
+ assertThat(qualification.getStartDate().getYear().getValue(), is("2020"));
+ assertThat(qualification.getStartDate().getMonth().getValue(), is("02"));
+ assertThat(qualification.getStartDate().getDay().getValue(), is("01"));
+ assertThat(qualification.getEndDate(), nullValue());
+ assertThat(qualification.getRoleTitle(), is("Researcher"));
+ assertThat(qualification.getDepartmentName(), is("4Science"));
+
+ Organization organization = qualification.getOrganization();
+ assertThat(organization, notNullValue());
+ assertThat(organization.getName(), is("4Science"));
+ assertThat(organization.getAddress(), notNullValue());
+ assertThat(organization.getAddress().getCountry(), is(Iso3166Country.IT));
+ assertThat(organization.getAddress().getCity(), is("Milan"));
+ assertThat(organization.getDisambiguatedOrganization(), notNullValue());
+ assertThat(organization.getDisambiguatedOrganization().getDisambiguatedOrganizationIdentifier(), is("12345"));
+ assertThat(organization.getDisambiguatedOrganization().getDisambiguationSource(), is("RINGGOLD"));
+ }
+
+ @Test
+ public void testDisambiguationFromOrgUnitHierarchyOnEmploymentCreationWithAncestor() {
+
+ context.turnOffAuthorisationSystem();
+
+ Item orgUnitGranfather = ItemBuilder.createItem(context, orgUnits)
+ .withTitle("4Science with rin")
+ .withOrgUnitCountry("IT")
+ .withOrgUnitLocality("Milan")
+ .withOrgUnitRinggoldIdentifier("12345")
+ .build();
+
+ Item orgUnitFather = ItemBuilder.createItem(context, orgUnits)
+ .withTitle("4Science without rin")
+ .withOrgUnitCountry("IT")
+ .withOrgUnitLocality("Milan")
+ .withParentOrganization("4Science with rin", orgUnitGranfather.getID().toString())
+ .build();
+
+
+ Item orgUnit = ItemBuilder.createItem(context, orgUnits)
+ .withTitle("4Science")
+ .withOrgUnitCountry("IT")
+ .withOrgUnitLocality("Milan")
+ .withParentOrganization("4Science without rin", orgUnitFather.getID().toString())
+ .build();
+
+ Item item = ItemBuilder.createItem(context, collection)
+ .withTitle("Test profile")
+ .withPersonAffiliation("4Science", orgUnit.getID().toString())
+ .withPersonAffiliationStartDate("2020-02")
+ .withPersonAffiliationEndDate(PLACEHOLDER_PARENT_METADATA_VALUE)
+ .withPersonAffiliationRole("Researcher")
+ .build();
+
+ context.restoreAuthSystemState();
+
+ List values = new ArrayList<>();
+ values.add(getMetadata(item, "oairecerif.person.affiliation", 0));
+ values.add(getMetadata(item, "oairecerif.affiliation.startDate", 0));
+ values.add(getMetadata(item, "oairecerif.affiliation.endDate", 0));
+ values.add(getMetadata(item, "oairecerif.affiliation.role", 0));
+
+ Object firstOrcidObject = profileSectionFactoryService.createOrcidObject(context, values, AFFILIATION);
+ assertThat(firstOrcidObject, instanceOf(Employment.class));
+ Employment qualification = (Employment) firstOrcidObject;
+ assertThat(qualification.getStartDate(), notNullValue());
+ assertThat(qualification.getStartDate().getYear().getValue(), is("2020"));
+ assertThat(qualification.getStartDate().getMonth().getValue(), is("02"));
+ assertThat(qualification.getStartDate().getDay().getValue(), is("01"));
+ assertThat(qualification.getEndDate(), nullValue());
+ assertThat(qualification.getRoleTitle(), is("Researcher"));
+ assertThat(qualification.getDepartmentName(), is("4Science"));
+
+ Organization organization = qualification.getOrganization();
+ assertThat(organization, notNullValue());
+ assertThat(organization.getName(), is("4Science"));
+ assertThat(organization.getAddress(), notNullValue());
+ assertThat(organization.getAddress().getCountry(), is(Iso3166Country.IT));
+ assertThat(organization.getAddress().getCity(), is("Milan"));
+ assertThat(organization.getDisambiguatedOrganization(), notNullValue());
+ assertThat(organization.getDisambiguatedOrganization().getDisambiguatedOrganizationIdentifier(), is("12345"));
+ assertThat(organization.getDisambiguatedOrganization().getDisambiguationSource(), is("RINGGOLD"));
+ }
+
+ @Test
+ public void testDisambiguationFromOrgUnitHierarchyOnEmploymentCreationWithNoId() {
+
+ context.turnOffAuthorisationSystem();
+
+ Item orgUnitWithRinId = ItemBuilder.createItem(context, orgUnits)
+ .withTitle("4Science with rin")
+ .withOrgUnitCountry("IT")
+ .withOrgUnitLocality("Milan")
+ .build();
+
+ Item orgUnit = ItemBuilder.createItem(context, orgUnits)
+ .withTitle("4Science")
+ .withOrgUnitCountry("IT")
+ .withOrgUnitLocality("Milan")
+ .withParentOrganization("4Science with rin", orgUnitWithRinId.getID().toString())
+ .build();
+
+ Item item = ItemBuilder.createItem(context, collection)
+ .withTitle("Test profile")
+ .withPersonAffiliation("4Science", orgUnit.getID().toString())
+ .withPersonAffiliationStartDate("2020-02")
+ .withPersonAffiliationEndDate(PLACEHOLDER_PARENT_METADATA_VALUE)
+ .withPersonAffiliationRole("Researcher")
+ .build();
+
+ context.restoreAuthSystemState();
+
+ List values = new ArrayList<>();
+ values.add(getMetadata(item, "oairecerif.person.affiliation", 0));
+ values.add(getMetadata(item, "oairecerif.affiliation.startDate", 0));
+ values.add(getMetadata(item, "oairecerif.affiliation.endDate", 0));
+ values.add(getMetadata(item, "oairecerif.affiliation.role", 0));
+
+ Object firstOrcidObject = profileSectionFactoryService.createOrcidObject(context, values, AFFILIATION);
+ assertThat(firstOrcidObject, instanceOf(Employment.class));
+ Employment qualification = (Employment) firstOrcidObject;
+ assertThat(qualification.getStartDate(), notNullValue());
+ assertThat(qualification.getStartDate().getYear().getValue(), is("2020"));
+ assertThat(qualification.getStartDate().getMonth().getValue(), is("02"));
+ assertThat(qualification.getStartDate().getDay().getValue(), is("01"));
+ assertThat(qualification.getEndDate(), nullValue());
+ assertThat(qualification.getRoleTitle(), is("Researcher"));
+ assertThat(qualification.getDepartmentName(), is("4Science"));
+
+ Organization organization = qualification.getOrganization();
+ assertThat(organization, notNullValue());
+ assertThat(organization.getName(), is("4Science"));
+ assertThat(organization.getAddress(), notNullValue());
+ assertThat(organization.getAddress().getCountry(), is(Iso3166Country.IT));
+ assertThat(organization.getAddress().getCity(), is("Milan"));
+ assertThat(organization.getDisambiguatedOrganization(), nullValue());
+ }
+
+
+
@Test
public void testQualificationCreation() {
context.turnOffAuthorisationSystem();
diff --git a/dspace-api/src/test/java/org/dspace/util/PersonNameUtilTest.java b/dspace-api/src/test/java/org/dspace/util/PersonNameUtilTest.java
index fe80bf143756..c0c5a0c02194 100644
--- a/dspace-api/src/test/java/org/dspace/util/PersonNameUtilTest.java
+++ b/dspace-api/src/test/java/org/dspace/util/PersonNameUtilTest.java
@@ -27,7 +27,8 @@ public class PersonNameUtilTest {
@Test
public void testWithAllNames() {
- Set variants = getAllNameVariants("Luca", "Giamminonni", List.of("Giamminonni, Luca", "Luke Giammo"));
+ Set variants = getAllNameVariants("Luca", "Giamminonni", List.of("Giamminonni, Luca",
+ "Luke Giammo"), "uuid");
assertThat(variants, containsInAnyOrder("Giamminonni Luca", "Luca Giamminonni",
"Giamminonni L.", "L. Giamminonni", "Giamminonni L", "L Giamminonni", "Luke Giammo", "Giammo Luke"));
@@ -37,7 +38,7 @@ public void testWithAllNames() {
public void testWithFirstNameComposedByTwoNames() {
Set variants = getAllNameVariants("Luca Paolo", "Giamminonni",
- List.of("Giamminonni, Luca", "Luke Giammo"));
+ List.of("Giamminonni, Luca", "Luke Giammo"), "uuid");
assertThat(variants, containsInAnyOrder("Giamminonni Luca Paolo", "Luca Paolo Giamminonni",
"Giamminonni Luca", "Luca Giamminonni", "Giamminonni Paolo", "Paolo Giamminonni",
@@ -51,7 +52,7 @@ public void testWithFirstNameComposedByTwoNames() {
public void testWithFirstNameComposedByThreeNames() {
Set variants = getAllNameVariants("Luca Paolo Claudio", "Giamminonni",
- List.of("Giamminonni, Luca", "Luke Giammo"));
+ List.of("Giamminonni, Luca", "Luke Giammo"), "uuid");
assertThat(variants, containsInAnyOrder("Giamminonni Luca Paolo Claudio", "Luca Paolo Claudio Giamminonni",
"Giamminonni Luca Claudio", "Luca Claudio Giamminonni", "Giamminonni Paolo Claudio",
@@ -69,7 +70,8 @@ public void testWithFirstNameComposedByThreeNames() {
@Test
public void testWithoutFirstAndLastName() {
- Set variants = getAllNameVariants(null, null, List.of("Giamminonni, Luca Fabio", "Luke Giammo"));
+ Set variants = getAllNameVariants(null, null, List.of("Giamminonni, Luca Fabio", "Luke Giammo"),
+ "uuid");
assertThat(variants, containsInAnyOrder("Giamminonni Luca Fabio", "Fabio Luca Giamminonni",
"Giamminonni Fabio Luca", "Luca Fabio Giamminonni", "Luca Giamminonni Fabio",
@@ -80,12 +82,13 @@ public void testWithoutFirstAndLastName() {
@Test
public void testWithAlreadyTruncatedName() {
- Set variants = getAllNameVariants("L.", "Giamminonni", List.of("Giamminonni, Luca"));
+ Set variants = getAllNameVariants("L.", "Giamminonni", List.of("Giamminonni, Luca"),
+ "uuid");
assertThat(variants, containsInAnyOrder("Giamminonni Luca", "Luca Giamminonni",
"Giamminonni L.", "L. Giamminonni", "Giamminonni L", "L Giamminonni"));
- variants = getAllNameVariants("L. P.", "Giamminonni", List.of("Giamminonni, Luca"));
+ variants = getAllNameVariants("L. P.", "Giamminonni", List.of("Giamminonni, Luca"), "uuid");
assertThat(variants, containsInAnyOrder("Giamminonni Luca", "Luca Giamminonni", "L. Giamminonni",
"Giamminonni L.", "P. Giamminonni", "Giamminonni P.", "Giamminonni L. P.", "L. P. Giamminonni",
@@ -97,7 +100,8 @@ public void testWithAlreadyTruncatedName() {
@Test
public void testWithAlreadyTruncatedNameOnFullName() {
- Set variants = getAllNameVariants("Luca", "Giamminonni", List.of("Giamminonni, L."));
+ Set variants = getAllNameVariants("Luca", "Giamminonni", List.of("Giamminonni, L."),
+ "uuid");
assertThat(variants, containsInAnyOrder("Giamminonni Luca", "Luca Giamminonni",
"Giamminonni L.", "L. Giamminonni", "Giamminonni L", "L Giamminonni"));
diff --git a/dspace-iiif/pom.xml b/dspace-iiif/pom.xml
index 08c2fe062265..3b56ba53e832 100644
--- a/dspace-iiif/pom.xml
+++ b/dspace-iiif/pom.xml
@@ -15,7 +15,7 @@
org.dspace
dspace-parent
- cris-2023.02.00
+ cris-2023.02.02-SNAPSHOT
..
diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml
index f3b7e23bf4e5..76718f44ba3c 100644
--- a/dspace-oai/pom.xml
+++ b/dspace-oai/pom.xml
@@ -8,7 +8,7 @@
dspace-parent
org.dspace
- cris-2023.02.00
+ cris-2023.02.02-SNAPSHOT
..
diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml
index f5acab4dfc70..eb63a67e4579 100644
--- a/dspace-rdf/pom.xml
+++ b/dspace-rdf/pom.xml
@@ -9,7 +9,7 @@
org.dspace
dspace-parent
- cris-2023.02.00
+ cris-2023.02.02-SNAPSHOT
..
diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml
index e1ade345ba72..257d0b3a91f8 100644
--- a/dspace-rest/pom.xml
+++ b/dspace-rest/pom.xml
@@ -3,7 +3,7 @@
org.dspace
dspace-rest
war
- cris-2023.02.00
+ cris-2023.02.02-SNAPSHOT
DSpace (Deprecated) REST Webapp
DSpace RESTful Web Services API. NOTE: this REST API is DEPRECATED.
Please consider using the REST API in the dspace-server-webapp instead!
@@ -12,7 +12,7 @@
org.dspace
dspace-parent
- cris-2023.02.00
+ cris-2023.02.02-SNAPSHOT
..
diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml
index a2add8447d8b..c94bfaeed6ae 100644
--- a/dspace-server-webapp/pom.xml
+++ b/dspace-server-webapp/pom.xml
@@ -15,7 +15,7 @@
org.dspace
dspace-parent
- cris-2023.02.00
+ cris-2023.02.02-SNAPSHOT
..
diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EditMetadataFeature.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EditMetadataFeature.java
index 820de57b7246..80a052224fb8 100644
--- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EditMetadataFeature.java
+++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/authorization/impl/EditMetadataFeature.java
@@ -60,12 +60,12 @@ public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLEx
) {
String defaultGroupUUID = configurationService.getProperty("edit.metadata.allowed-group");
if (StringUtils.isBlank(defaultGroupUUID)) {
- return authorizeServiceRestUtil.authorizeActionBoolean(context, object,DSpaceRestPermission.WRITE);
+ return authorizeServiceRestUtil.authorizeActionBoolean(context, object, DSpaceRestPermission.WRITE);
}
Group defaultGroup = StringUtils.isNotBlank(defaultGroupUUID) ?
groupService.find(context, UUID.fromString(defaultGroupUUID)) : null;
if (Objects.nonNull(defaultGroup) && groupService.isMember(context, defaultGroup)) {
- return authorizeServiceRestUtil.authorizeActionBoolean(context, object,DSpaceRestPermission.WRITE);
+ return authorizeServiceRestUtil.authorizeActionBoolean(context, object, DSpaceRestPermission.WRITE);
}
}
return false;
diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java
index b64da66af9cf..8c7e89565371 100644
--- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java
+++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java
@@ -40,6 +40,7 @@
import org.dspace.app.rest.model.patch.Patch;
import org.dspace.app.rest.repository.handler.service.UriListHandlerService;
import org.dspace.authorize.AuthorizeException;
+import org.dspace.authorize.service.AuthorizeService;
import org.dspace.content.Bundle;
import org.dspace.content.Collection;
import org.dspace.content.Item;
@@ -106,6 +107,9 @@ public class ItemRestRepository extends DSpaceObjectRestRepository- findAll(Context context, Pageable pageable) {
protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, UUID id,
Patch patch) throws AuthorizeException, SQLException {
Item item = itemService.find(context, id);
- if (!editMetadataFeature.isAuthorized(context, converter.toRest(item, utils.obtainProjection()))) {
+ if (!authorizeService.isAdmin(context) &&
+ !editMetadataFeature.isAuthorized(context, converter.toRest(item, utils.obtainProjection()))) {
throw new AccessDeniedException("Current user not authorized for this operation");
}
patchDSpaceObject(apiCategory, model, id, patch);
@@ -350,13 +355,13 @@ protected ItemRest put(Context context, HttpServletRequest request, String apiCa
} catch (IOException e1) {
throw new UnprocessableEntityException("Error parsing request body", e1);
}
- if (!editMetadataFeature.isAuthorized(context, itemRest)) {
- throw new AccessDeniedException("Current user not authorized for this operation");
- }
Item item = itemService.find(context, uuid);
if (item == null) {
throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + uuid + " not found");
}
+ if (!authorizeService.isAdmin(context) && !editMetadataFeature.isAuthorized(context, itemRest)) {
+ throw new AccessDeniedException("Current user not authorized for this operation");
+ }
if (StringUtils.equals(uuid.toString(), itemRest.getId())) {
metadataConverter.setMetadata(context, item, itemRest.getMetadata());
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 29caf8def121..7e0edf08bac9 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
@@ -4552,8 +4552,8 @@ public void putItemMetadataWithUserNotPartOfGroupConfigured() throws Exception {
itemRest.setInArchive(true);
itemRest.setDiscoverable(true);
itemRest.setWithdrawn(false);
- String token = getAuthToken(admin.getEmail(), password);
- MvcResult mvcResult = getClient(token).perform(post("/api/core/items?owningCollection=" +
+ String adminToken = getAuthToken(admin.getEmail(), password);
+ MvcResult mvcResult = getClient(adminToken).perform(post("/api/core/items?owningCollection=" +
col1.getID().toString())
.content(mapper.writeValueAsBytes(itemRest))
.contentType(contentType))
@@ -4569,12 +4569,25 @@ public void putItemMetadataWithUserNotPartOfGroupConfigured() throws Exception {
itemRest.setHandle(itemHandleString);
Group group = GroupBuilder.createGroup(context).build();
configurationService.setProperty("edit.metadata.allowed-group", group.getID());
+ // add write rights to the user
+ ResourcePolicyBuilder.createResourcePolicy(context)
+ .withUser(eperson)
+ .withAction(WRITE)
+ .withDspaceObject(itemService.find(context, UUID.fromString(itemUuidString)))
+ .build();
+
context.restoreAuthSystemState();
+ String token = getAuthToken(eperson.getEmail(), password);
// expect forbidden, the user is not part of the group set in property {{edit.metadata.allowed-group}}
getClient(token).perform(put("/api/core/items/" + itemUuidString)
.content(mapper.writeValueAsBytes(itemRest))
.contentType(contentType))
.andExpect(status().isForbidden());
+ // admins should still be able to use put
+ getClient(adminToken).perform(put("/api/core/items/" + itemUuidString)
+ .content(mapper.writeValueAsBytes(itemRest))
+ .contentType(contentType))
+ .andExpect(status().isOk());
} finally {
ItemBuilder.deleteItem(UUID.fromString(itemUuidString));
}
@@ -4635,7 +4648,7 @@ public void putItemMetadataWithUserPartOfGroupConfigured() throws Exception {
context.restoreAuthSystemState();
token = getAuthToken(eperson.getEmail(), password);
configurationService.setProperty("edit.metadata.allowed-group", group.getID());
- // expect forbidden, the user is not part of the group set in property {{edit.metadata.allowed-group}}
+ // expect ok, the user is part of the group set in property {{edit.metadata.allowed-group}}
getClient(token).perform(put("/api/core/items/" + itemUuidString)
.content(mapper.writeValueAsBytes(itemRest))
.contentType(contentType))
@@ -4907,7 +4920,7 @@ public void findVersionForItemWithoutVersionsWithVersioningDisabledTest() throws
public void patchItemMetadataWithUserPartOfGroupConfigured() throws Exception {
context.turnOffAuthorisationSystem();
// add admin person as member to the group
- Group group = GroupBuilder.createGroup(context).addMember(admin).build();
+ Group group = GroupBuilder.createGroup(context).addMember(eperson).build();
groupService.update(context, group);
context.commit();
// ** GIVEN **
@@ -4930,15 +4943,19 @@ public void patchItemMetadataWithUserPartOfGroupConfigured() throws Exception {
.build();
// add write permission to the user admin
ResourcePolicyBuilder.createResourcePolicy(context)
- .withUser(admin)
+ .withUser(eperson)
.withAction(WRITE)
.withDspaceObject(itemService.find(context, item.getID()))
.build();
context.restoreAuthSystemState();
configurationService.setProperty("edit.metadata.allowed-group", group.getID());
- String token = getAuthToken(admin.getEmail(), password);
+ String token = getAuthToken(eperson.getEmail(), password);
List ops = new ArrayList();
- ReplaceOperation replaceOperation = new ReplaceOperation("/withdrawn", true);
+ List