Merge pull request #3046 from 4Science/CSTPER-221

Metadata identifier for Communities and Collections
This commit is contained in:
Tim Donohue
2020-11-23 09:21:24 -06:00
committed by GitHub
20 changed files with 595 additions and 95 deletions

View File

@@ -55,6 +55,8 @@ import org.dspace.eperson.service.SubscribeService;
import org.dspace.event.Event;
import org.dspace.harvest.HarvestedCollection;
import org.dspace.harvest.service.HarvestedCollectionService;
import org.dspace.identifier.IdentifierException;
import org.dspace.identifier.service.IdentifierService;
import org.dspace.workflow.factory.WorkflowServiceFactory;
import org.dspace.xmlworkflow.WorkflowConfigurationException;
import org.dspace.xmlworkflow.factory.XmlWorkflowFactory;
@@ -92,6 +94,8 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl<Collection> i
protected CommunityService communityService;
@Autowired(required = true)
protected GroupService groupService;
@Autowired(required = true)
protected IdentifierService identifierService;
@Autowired(required = true)
protected LicenseService licenseService;
@@ -131,13 +135,6 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl<Collection> i
//Add our newly created collection to our community, authorization checks occur in THIS method
communityService.addCollection(context, community, newCollection);
//Update our community so we have a collection identifier
if (handle == null) {
handleService.createHandle(context, newCollection);
} else {
handleService.createHandle(context, newCollection, handle);
}
// create the default authorization policy for collections
// of 'anonymous' READ
Group anonymousGroup = groupService.findByName(context, Group.ANONYMOUS);
@@ -150,6 +147,18 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl<Collection> i
authorizeService
.createResourcePolicy(context, newCollection, anonymousGroup, null, Constants.DEFAULT_BITSTREAM_READ, null);
collectionDAO.save(context, newCollection);
//Update our collection so we have a collection identifier
try {
if (handle == null) {
identifierService.register(context, newCollection);
} else {
identifierService.register(context, newCollection, handle);
}
} catch (IllegalStateException | IdentifierException ex) {
throw new IllegalStateException(ex);
}
context.addEvent(new Event(Event.CREATE, Constants.COLLECTION,
newCollection.getID(), newCollection.getHandle(),
@@ -159,7 +168,6 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl<Collection> i
"collection_id=" + newCollection.getID())
+ ",handle=" + newCollection.getHandle());
collectionDAO.save(context, newCollection);
return newCollection;
}

View File

@@ -37,6 +37,8 @@ import org.dspace.core.LogManager;
import org.dspace.eperson.Group;
import org.dspace.eperson.service.GroupService;
import org.dspace.event.Event;
import org.dspace.identifier.IdentifierException;
import org.dspace.identifier.service.IdentifierService;
import org.springframework.beans.factory.annotation.Autowired;
/**
@@ -69,6 +71,8 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl<Community> imp
protected BitstreamService bitstreamService;
@Autowired(required = true)
protected SiteService siteService;
@Autowired(required = true)
protected IdentifierService identifierService;
protected CommunityServiceImpl() {
super();
@@ -90,17 +94,6 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl<Community> imp
Community newCommunity = communityDAO.create(context, new Community());
try {
if (handle == null) {
handleService.createHandle(context, newCommunity);
} else {
handleService.createHandle(context, newCommunity, handle);
}
} catch (IllegalStateException ie) {
//If an IllegalStateException is thrown, then an existing object is already using this handle
throw ie;
}
if (parent != null) {
parent.addSubCommunity(newCommunity);
newCommunity.addParentCommunity(parent);
@@ -115,14 +108,24 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl<Community> imp
communityDAO.save(context, newCommunity);
try {
if (handle == null) {
identifierService.register(context, newCommunity);
} else {
identifierService.register(context, newCommunity, handle);
}
} catch (IllegalStateException | IdentifierException ex) {
throw new IllegalStateException(ex);
}
context.addEvent(new Event(Event.CREATE, Constants.COMMUNITY, newCommunity.getID(), newCommunity.getHandle(),
getIdentifiers(context, newCommunity)));
getIdentifiers(context, newCommunity)));
// if creating a top-level Community, simulate an ADD event at the Site.
if (parent == null) {
context.addEvent(new Event(Event.ADD, Constants.SITE, siteService.findSite(context).getID(),
Constants.COMMUNITY, newCommunity.getID(), newCommunity.getHandle(),
getIdentifiers(context, newCommunity)));
Constants.COMMUNITY, newCommunity.getID(), newCommunity.getHandle(),
getIdentifiers(context, newCommunity)));
}
log.info(LogManager.getHeader(context, "create_community",

View File

@@ -169,6 +169,10 @@ public class DOIIdentifierProvider
@Override
public String register(Context context, DSpaceObject dso)
throws IdentifierException {
if (!(dso instanceof Item)) {
// DOI are currently assigned only to Item
return null;
}
String doi = mint(context, dso);
// register tries to reserve doi if it's not already.
// So we don't have to reserve it here.
@@ -179,6 +183,10 @@ public class DOIIdentifierProvider
@Override
public void register(Context context, DSpaceObject dso, String identifier)
throws IdentifierException {
if (!(dso instanceof Item)) {
// DOI are currently assigned only to Item
return;
}
String doi = doiService.formatIdentifier(identifier);
DOI doiRow = null;

View File

@@ -148,6 +148,10 @@ public class EZIDIdentifierProvider
throws IdentifierException {
log.debug("register {}", dso);
if (!(dso instanceof Item)) {
// DOI are currently assigned only to Item
return null;
}
DSpaceObjectService<DSpaceObject> dsoService = contentServiceFactory.getDSpaceObjectService(dso);
List<MetadataValue> identifiers = dsoService.getMetadata(dso, MD_SCHEMA, DOI_ELEMENT, DOI_QUALIFIER, null);
for (MetadataValue identifier : identifiers) {
@@ -171,6 +175,10 @@ public class EZIDIdentifierProvider
public void register(Context context, DSpaceObject object, String identifier) {
log.debug("register {} as {}", object, identifier);
if (!(object instanceof Item)) {
// DOI are currently assigned only to Item
return;
}
EZIDResponse response;
try {
EZIDRequest request = requestFactory.getInstance(loadAuthority(),

View File

@@ -13,11 +13,14 @@ import java.util.List;
import org.apache.logging.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.content.MetadataValue;
import org.dspace.content.service.ItemService;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.DSpaceObjectService;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Context;
import org.dspace.core.LogManager;
@@ -48,7 +51,7 @@ public class HandleIdentifierProvider extends IdentifierProvider {
@Autowired(required = true)
protected HandleService handleService;
@Autowired(required = true)
protected ItemService itemService;
protected ContentServiceFactory contentServiceFactory;
@Override
public boolean supports(Class<? extends Identifier> identifier) {
@@ -91,7 +94,7 @@ public class HandleIdentifierProvider extends IdentifierProvider {
String id = mint(context, dso);
// move canonical to point the latest version
if (dso instanceof Item) {
if (dso instanceof Item || dso instanceof Collection || dso instanceof Community) {
Item item = (Item) dso;
populateHandleMetadata(context, item, id);
}
@@ -108,7 +111,7 @@ public class HandleIdentifierProvider extends IdentifierProvider {
public void register(Context context, DSpaceObject dso, String identifier) {
try {
handleService.createHandle(context, dso, identifier);
if (dso instanceof Item) {
if (dso instanceof Item || dso instanceof Collection || dso instanceof Community) {
Item item = (Item) dso;
populateHandleMetadata(context, item, identifier);
}
@@ -220,23 +223,25 @@ public class HandleIdentifierProvider extends IdentifierProvider {
return prefix;
}
protected void populateHandleMetadata(Context context, Item item, String handle)
throws SQLException, IOException, AuthorizeException {
protected void populateHandleMetadata(Context context, DSpaceObject dso, String handle)
throws SQLException, IOException, AuthorizeException {
String handleref = handleService.getCanonicalForm(handle);
DSpaceObjectService<DSpaceObject> dsoService = contentServiceFactory.getDSpaceObjectService(dso);
// Add handle as identifier.uri DC value.
// First check that identifier doesn't already exist.
boolean identifierExists = false;
List<MetadataValue> identifiers = itemService
.getMetadata(item, MetadataSchemaEnum.DC.getName(), "identifier", "uri", Item.ANY);
List<MetadataValue> identifiers = dsoService
.getMetadata(dso, MetadataSchemaEnum.DC.getName(), "identifier", "uri", Item.ANY);
for (MetadataValue identifier : identifiers) {
if (handleref.equals(identifier.getValue())) {
identifierExists = true;
}
}
if (!identifierExists) {
itemService.addMetadata(context, item, MetadataSchemaEnum.DC.getName(),
"identifier", "uri", null, handleref);
dsoService.addMetadata(context, dso, MetadataSchemaEnum.DC.getName(),
"identifier", "uri", null, handleref);
}
}
}

View File

@@ -97,7 +97,7 @@ public class IdentifierServiceImpl implements IdentifierService {
for (IdentifierProvider service : providers) {
service.register(context, dso);
}
//Update our item
//Update our item / collection / community
contentServiceFactory.getDSpaceObjectService(dso).update(context, dso);
}
@@ -117,7 +117,7 @@ public class IdentifierServiceImpl implements IdentifierService {
throw new IdentifierException("Cannot register identifier: Didn't "
+ "find a provider that supports this identifier.");
}
//Update our item
//Update our item / collection / community
contentServiceFactory.getDSpaceObjectService(object).update(context, object);
}

View File

@@ -17,11 +17,14 @@ import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.MetadataSchemaEnum;
import org.dspace.content.MetadataValue;
import org.dspace.content.service.ItemService;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.DSpaceObjectService;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Constants;
import org.dspace.core.Context;
@@ -65,7 +68,7 @@ public class VersionedHandleIdentifierProvider extends IdentifierProvider {
private HandleService handleService;
@Autowired(required = true)
private ItemService itemService;
protected ContentServiceFactory contentServiceFactory;
@Override
public boolean supports(Class<? extends Identifier> identifier) {
@@ -107,8 +110,8 @@ public class VersionedHandleIdentifierProvider extends IdentifierProvider {
public String register(Context context, DSpaceObject dso) {
String id = mint(context, dso);
try {
if (dso instanceof Item) {
populateHandleMetadata(context, (Item) dso, id);
if (dso instanceof Item || dso instanceof Collection || dso instanceof Community) {
populateHandleMetadata(context, dso, id);
}
} catch (Exception e) {
log.error(LogManager.getHeader(context, "Error while attempting to create handle",
@@ -410,16 +413,17 @@ public class VersionedHandleIdentifierProvider extends IdentifierProvider {
return identifier;
}
protected void populateHandleMetadata(Context context, Item item, String handle)
protected void populateHandleMetadata(Context context, DSpaceObject dso, String handle)
throws SQLException, IOException, AuthorizeException {
String handleref = handleService.getCanonicalForm(handle);
// we want to remove the old handle and insert the new. To do so, we
// load all identifiers, clear the metadata field, re add all
// identifiers which are not from type handle and add the new handle.
List<MetadataValue> identifiers = itemService.getMetadata(item,
DSpaceObjectService<DSpaceObject> dsoService = contentServiceFactory.getDSpaceObjectService(dso);
List<MetadataValue> identifiers = dsoService.getMetadata(dso,
MetadataSchemaEnum.DC.getName(), "identifier", "uri",
Item.ANY);
itemService.clearMetadata(context, item, MetadataSchemaEnum.DC.getName(),
dsoService.clearMetadata(context, dso, MetadataSchemaEnum.DC.getName(),
"identifier", "uri", Item.ANY);
for (MetadataValue identifier : identifiers) {
if (this.supports(identifier.getValue())) {
@@ -428,8 +432,8 @@ public class VersionedHandleIdentifierProvider extends IdentifierProvider {
continue;
}
log.debug("Preserving identifier " + identifier.getValue());
itemService.addMetadata(context,
item,
dsoService.addMetadata(context,
dso,
identifier.getMetadataField(),
identifier.getLanguage(),
identifier.getValue(),
@@ -439,9 +443,9 @@ public class VersionedHandleIdentifierProvider extends IdentifierProvider {
// Add handle as identifier.uri DC value.
if (StringUtils.isNotBlank(handleref)) {
itemService.addMetadata(context, item, MetadataSchemaEnum.DC.getName(),
dsoService.addMetadata(context, dso, MetadataSchemaEnum.DC.getName(),
"identifier", "uri", null, handleref);
}
itemService.update(context, item);
dsoService.update(context, dso);
}
}

View File

@@ -0,0 +1,58 @@
/**
* 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.storage.rdbms.migration;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.text.StringSubstitutor;
import org.dspace.handle.service.HandleService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.storage.rdbms.DatabaseUtils;
import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context;
/**
* Insert a 'dc.idendifier.uri' metadata record for each Community and Collection in the database.
* The value is calculated concatenating the canonicalPrefix extracted from the configuration
* (default is "http://hdl.handle.net/) and the object's handle suffix stored inside the handle table.
*
* @author Alessandro Martelli (alessandro.martelli at 4science.it)
*/
public class V7_0_2020_10_31__CollectionCommunity_Metadata_Handle extends BaseJavaMigration {
// Size of migration script run
protected Integer migration_file_size = -1;
@Override
public void migrate(Context context) throws Exception {
HandleService handleService = DSpaceServicesFactory
.getInstance().getServiceManager().getServicesByType(HandleService.class).get(0);
String dbtype = DatabaseUtils.getDbType(context.getConnection());
String sqlMigrationPath = "org/dspace/storage/rdbms/sqlmigration/metadata/" + dbtype + "/";
String dataMigrateSQL = MigrationUtils.getResourceAsString(
sqlMigrationPath + "V7.0_2020.10.31__CollectionCommunity_Metadata_Handle.sql");
// Replace ${handle.canonical.prefix} variable in SQL script with value from Configuration
Map<String, String> valuesMap = new HashMap<String, String>();
valuesMap.put("handle.canonical.prefix", handleService.getCanonicalPrefix());
StringSubstitutor sub = new StringSubstitutor(valuesMap);
dataMigrateSQL = sub.replace(dataMigrateSQL);
DatabaseUtils.executeSql(context.getConnection(), dataMigrateSQL);
migration_file_size = dataMigrateSQL.length();
}
@Override
public Integer getChecksum() {
return migration_file_size;
}
}

View File

@@ -0,0 +1,90 @@
--
-- 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/
--
-- ===============================================================
-- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
--
-- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED
-- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE.
-- http://flywaydb.org/
-- ===============================================================
-------------------------------------------------------------
-- This will create COMMUNITY handle metadata
-------------------------------------------------------------
insert into metadatavalue (metadata_field_id, text_value, text_lang, place, authority, confidence, dspace_object_id)
select distinct
T1.metadata_field_id as metadata_field_id,
concat('${handle.canonical.prefix}', h.handle) as text_value,
null as text_lang, 0 as place,
null as authority,
-1 as confidence,
c.uuid as dspace_object_id
from community c
left outer join handle h on h.resource_id = c.uuid
left outer join metadatavalue mv on mv.dspace_object_id = c.uuid
left outer join metadatafieldregistry mfr on mv.metadata_field_id = mfr.metadata_field_id
left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id
cross join (select mfr.metadata_field_id as metadata_field_id from metadatafieldregistry mfr
left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id
where msr.short_id = 'dc'
and mfr.element = 'identifier'
and mfr.qualifier = 'uri') T1
where uuid not in (
select c.uuid as uuid from community c
left outer join handle h on h.resource_id = c.uuid
left outer join metadatavalue mv on mv.dspace_object_id = c.uuid
left outer join metadatafieldregistry mfr on mv.metadata_field_id = mfr.metadata_field_id
left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id
where msr.short_id = 'dc'
and mfr.element = 'identifier'
and mfr.qualifier = 'uri'
)
;
-------------------------------------------------------------
-- This will create COLLECTION handle metadata
-------------------------------------------------------------
insert into metadatavalue (metadata_field_id, text_value, text_lang, place, authority, confidence, dspace_object_id)
select distinct
T1.metadata_field_id as metadata_field_id,
concat('${handle.canonical.prefix}', h.handle) as text_value,
null as text_lang, 0 as place,
null as authority,
-1 as confidence,
c.uuid as dspace_object_id
from collection c
left outer join handle h on h.resource_id = c.uuid
left outer join metadatavalue mv on mv.dspace_object_id = c.uuid
left outer join metadatafieldregistry mfr on mv.metadata_field_id = mfr.metadata_field_id
left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id
cross join (select mfr.metadata_field_id as metadata_field_id from metadatafieldregistry mfr
left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id
where msr.short_id = 'dc'
and mfr.element = 'identifier'
and mfr.qualifier = 'uri') T1
where uuid not in (
select c.uuid as uuid from collection c
left outer join handle h on h.resource_id = c.uuid
left outer join metadatavalue mv on mv.dspace_object_id = c.uuid
left outer join metadatafieldregistry mfr on mv.metadata_field_id = mfr.metadata_field_id
left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id
where msr.short_id = 'dc'
and mfr.element = 'identifier'
and mfr.qualifier = 'uri'
)
;

View File

@@ -0,0 +1,90 @@
--
-- 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/
--
-- ===============================================================
-- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
--
-- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED
-- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE.
-- http://flywaydb.org/
-- ===============================================================
-------------------------------------------------------------
-- This will create COMMUNITY handle metadata
-------------------------------------------------------------
insert into metadatavalue (metadata_field_id, text_value, text_lang, place, authority, confidence, dspace_object_id)
select distinct
T1.metadata_field_id as metadata_field_id,
concat('${handle.canonical.prefix}', h.handle) as text_value,
null as text_lang, 0 as place,
null as authority,
-1 as confidence,
c.uuid as dspace_object_id
from community c
left outer join handle h on h.resource_id = c.uuid
left outer join metadatavalue mv on mv.dspace_object_id = c.uuid
left outer join metadatafieldregistry mfr on mv.metadata_field_id = mfr.metadata_field_id
left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id
cross join (select mfr.metadata_field_id as metadata_field_id from metadatafieldregistry mfr
left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id
where msr.short_id = 'dc'
and mfr.element = 'identifier'
and mfr.qualifier = 'uri') T1
where uuid not in (
select c.uuid as uuid from community c
left outer join handle h on h.resource_id = c.uuid
left outer join metadatavalue mv on mv.dspace_object_id = c.uuid
left outer join metadatafieldregistry mfr on mv.metadata_field_id = mfr.metadata_field_id
left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id
where msr.short_id = 'dc'
and mfr.element = 'identifier'
and mfr.qualifier = 'uri'
)
;
-------------------------------------------------------------
-- This will create COLLECTION handle metadata
-------------------------------------------------------------
insert into metadatavalue (metadata_field_id, text_value, text_lang, place, authority, confidence, dspace_object_id)
select distinct
T1.metadata_field_id as metadata_field_id,
concat('${handle.canonical.prefix}', h.handle) as text_value,
null as text_lang, 0 as place,
null as authority,
-1 as confidence,
c.uuid as dspace_object_id
from collection c
left outer join handle h on h.resource_id = c.uuid
left outer join metadatavalue mv on mv.dspace_object_id = c.uuid
left outer join metadatafieldregistry mfr on mv.metadata_field_id = mfr.metadata_field_id
left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id
cross join (select mfr.metadata_field_id as metadata_field_id from metadatafieldregistry mfr
left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id
where msr.short_id = 'dc'
and mfr.element = 'identifier'
and mfr.qualifier = 'uri') T1
where uuid not in (
select c.uuid as uuid from collection c
left outer join handle h on h.resource_id = c.uuid
left outer join metadatavalue mv on mv.dspace_object_id = c.uuid
left outer join metadatafieldregistry mfr on mv.metadata_field_id = mfr.metadata_field_id
left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id
where msr.short_id = 'dc'
and mfr.element = 'identifier'
and mfr.qualifier = 'uri'
)
;

View File

@@ -0,0 +1,90 @@
--
-- 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/
--
-- ===============================================================
-- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
--
-- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED
-- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE.
-- http://flywaydb.org/
-- ===============================================================
-------------------------------------------------------------
-- This will create COMMUNITY handle metadata
-------------------------------------------------------------
insert into metadatavalue (metadata_field_id, text_value, text_lang, place, authority, confidence, dspace_object_id)
select distinct
T1.metadata_field_id as metadata_field_id,
concat('${handle.canonical.prefix}', h.handle) as text_value,
null as text_lang, 0 as place,
null as authority,
-1 as confidence,
c.uuid as dspace_object_id
from community c
left outer join handle h on h.resource_id = c.uuid
left outer join metadatavalue mv on mv.dspace_object_id = c.uuid
left outer join metadatafieldregistry mfr on mv.metadata_field_id = mfr.metadata_field_id
left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id
cross join (select mfr.metadata_field_id as metadata_field_id from metadatafieldregistry mfr
left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id
where msr.short_id = 'dc'
and mfr.element = 'identifier'
and mfr.qualifier = 'uri') T1
where uuid not in (
select c.uuid as uuid from community c
left outer join handle h on h.resource_id = c.uuid
left outer join metadatavalue mv on mv.dspace_object_id = c.uuid
left outer join metadatafieldregistry mfr on mv.metadata_field_id = mfr.metadata_field_id
left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id
where msr.short_id = 'dc'
and mfr.element = 'identifier'
and mfr.qualifier = 'uri'
)
;
-------------------------------------------------------------
-- This will create COLLECTION handle metadata
-------------------------------------------------------------
insert into metadatavalue (metadata_field_id, text_value, text_lang, place, authority, confidence, dspace_object_id)
select distinct
T1.metadata_field_id as metadata_field_id,
concat('${handle.canonical.prefix}', h.handle) as text_value,
null as text_lang, 0 as place,
null as authority,
-1 as confidence,
c.uuid as dspace_object_id
from collection c
left outer join handle h on h.resource_id = c.uuid
left outer join metadatavalue mv on mv.dspace_object_id = c.uuid
left outer join metadatafieldregistry mfr on mv.metadata_field_id = mfr.metadata_field_id
left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id
cross join (select mfr.metadata_field_id as metadata_field_id from metadatafieldregistry mfr
left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id
where msr.short_id = 'dc'
and mfr.element = 'identifier'
and mfr.qualifier = 'uri') T1
where uuid not in (
select c.uuid as uuid from collection c
left outer join handle h on h.resource_id = c.uuid
left outer join metadatavalue mv on mv.dspace_object_id = c.uuid
left outer join metadatafieldregistry mfr on mv.metadata_field_id = mfr.metadata_field_id
left outer join metadataschemaregistry msr on mfr.metadata_schema_id = msr.metadata_schema_id
where msr.short_id = 'dc'
and mfr.element = 'identifier'
and mfr.qualifier = 'uri'
)
;

View File

@@ -38,6 +38,8 @@ import org.dspace.core.factory.CoreServiceFactory;
import org.dspace.core.service.LicenseService;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.services.ConfigurationService;
import org.dspace.utils.DSpace;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -159,6 +161,7 @@ public class CollectionTest extends AbstractDSpaceObjectTest {
public void testCreate() throws Exception {
// Allow Community ADD perms
doNothing().when(authorizeServiceSpy).authorizeAction(context, owningCommunity, Constants.ADD);
doNothing().when(authorizeServiceSpy).authorizeAction(context, owningCommunity, Constants.ADD, true);
Collection created = collectionService.create(context, owningCommunity);
assertThat("testCreate 0", created, notNullValue());
@@ -172,6 +175,14 @@ public class CollectionTest extends AbstractDSpaceObjectTest {
public void testCreateWithValidHandle() throws Exception {
// Allow Community ADD perms
doNothing().when(authorizeServiceSpy).authorizeAction(context, owningCommunity, Constants.ADD);
doNothing().when(authorizeServiceSpy).authorizeAction(context, owningCommunity, Constants.ADD, true);
// provide additional prefixes to the configuration in order to support them
final ConfigurationService configurationService = new DSpace().getConfigurationService();
String handleAdditionalPrefixes = configurationService.getProperty("handle.additional.prefixes");
try {
configurationService.setProperty("handle.additional.prefixes", "987654321");
// test creating collection with a specified handle which is NOT already in use
// (this handle should not already be used by system, as it doesn't start with "1234567689" prefix)
@@ -180,6 +191,10 @@ public class CollectionTest extends AbstractDSpaceObjectTest {
// check that collection was created, and that its handle was set to proper value
assertThat("testCreateWithValidHandle 0", created, notNullValue());
assertThat("testCreateWithValidHandle 1", created.getHandle(), equalTo("987654321/100"));
} finally {
configurationService.setProperty("handle.additional.prefixes", handleAdditionalPrefixes);
}
}

View File

@@ -19,6 +19,7 @@ import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -35,7 +36,10 @@ import org.dspace.authorize.factory.AuthorizeServiceFactory;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.services.ConfigurationService;
import org.dspace.utils.DSpace;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -169,6 +173,7 @@ public class CommunityTest extends AbstractDSpaceObjectTest {
// Below settings default to Full Admin Rights (but not Community Admin rights)
// Allow full Admin perms
when(authorizeServiceSpy.isAdmin(context)).thenReturn(true);
when(authorizeServiceSpy.isAdmin(context, eperson)).thenReturn(true);
//Test that a full Admin can create a Community without a parent (Top-Level Community)
Community created = communityService.create(null, context);
@@ -206,6 +211,14 @@ public class CommunityTest extends AbstractDSpaceObjectTest {
public void testCreateWithValidHandle() throws Exception {
// Allow full Admin perms
when(authorizeServiceSpy.isAdmin(context)).thenReturn(true);
doReturn(true).when(authorizeServiceSpy).isAdmin(eq(context), any(EPerson.class));
// provide additional prefixes to the configuration in order to support them
final ConfigurationService configurationService = new DSpace().getConfigurationService();
String handleAdditionalPrefixes = configurationService.getProperty("handle.additional.prefixes");
try {
configurationService.setProperty("handle.additional.prefixes", "987654321");
// test creating community with a specified handle which is NOT already in use
// (this handle should not already be used by system, as it doesn't start with "1234567689" prefix)
@@ -214,6 +227,10 @@ public class CommunityTest extends AbstractDSpaceObjectTest {
// check that community was created, and that its handle was set to proper value
assertThat("testCreateWithValidHandle 0", created, notNullValue());
assertThat("testCreateWithValidHandle 1", created.getHandle(), equalTo("987654321/100c"));
} finally {
configurationService.setProperty("handle.additional.prefixes", handleAdditionalPrefixes);
}
}
@@ -602,6 +619,7 @@ public class CommunityTest extends AbstractDSpaceObjectTest {
public void testCreateCollectionAuth() throws Exception {
// Allow current Community ADD perms
doNothing().when(authorizeServiceSpy).authorizeAction(context, c, Constants.ADD);
doNothing().when(authorizeServiceSpy).authorizeAction(context, c, Constants.ADD, true);
Collection result = collectionService.create(context, c);
assertThat("testCreateCollectionAuth 0", result, notNullValue());
@@ -628,6 +646,7 @@ public class CommunityTest extends AbstractDSpaceObjectTest {
public void testAddCollectionAuth() throws Exception {
// Allow current Community ADD perms
doNothing().when(authorizeServiceSpy).authorizeAction(context, c, Constants.ADD);
doNothing().when(authorizeServiceSpy).authorizeAction(context, c, Constants.ADD, true);
Collection col = collectionService.create(context, c);
c.addCollection(col);
@@ -929,7 +948,8 @@ public class CommunityTest extends AbstractDSpaceObjectTest {
@SuppressWarnings("ObjectEqualsNull")
public void testEquals() throws SQLException, AuthorizeException {
// Allow full Admin perms (just to create top-level community)
when(authorizeServiceSpy.isAdmin(context)).thenReturn(true);
doReturn(true).when(authorizeServiceSpy).isAdmin(eq(context));
doReturn(true).when(authorizeServiceSpy).isAdmin(eq(context), any(EPerson.class));
assertFalse("testEquals 0", c.equals(null));
assertFalse("testEquals 1", c.equals(communityService.create(null, context)));

View File

@@ -75,6 +75,7 @@ public class MetadataConverter implements DSpaceConverter<MetadataValueList, Met
/**
* Sets a DSpace object's domain metadata values from a rest representation.
* Any existing metadata value is deleted or overwritten.
*
* @param context the context to use.
* @param dso the DSpace object.
@@ -82,10 +83,52 @@ public class MetadataConverter implements DSpaceConverter<MetadataValueList, Met
* @throws SQLException if a database error occurs.
* @throws AuthorizeException if an authorization error occurs.
*/
public void setMetadata(Context context, DSpaceObject dso, MetadataRest metadataRest)
public <T extends DSpaceObject> void setMetadata(Context context, T dso, MetadataRest metadataRest)
throws SQLException, AuthorizeException {
DSpaceObjectService dsoService = contentServiceFactory.getDSpaceObjectService(dso);
DSpaceObjectService<T> dsoService = contentServiceFactory.getDSpaceObjectService(dso);
dsoService.clearMetadata(context, dso, Item.ANY, Item.ANY, Item.ANY, Item.ANY);
persistMetadataRest(context, dso, metadataRest, dsoService);
}
/**
* Add to a DSpace object's domain metadata values from a rest representation.
* Any existing metadata value is preserved.
*
* @param context the context to use.
* @param dso the DSpace object.
* @param metadataRest the rest representation of the new metadata.
* @throws SQLException if a database error occurs.
* @throws AuthorizeException if an authorization error occurs.
*/
public <T extends DSpaceObject> void addMetadata(Context context, T dso, MetadataRest metadataRest)
throws SQLException, AuthorizeException {
DSpaceObjectService<T> dsoService = contentServiceFactory.getDSpaceObjectService(dso);
persistMetadataRest(context, dso, metadataRest, dsoService);
}
/**
* Merge into a DSpace object's domain metadata values from a rest representation.
* Any existing metadata value is preserved or overwritten with the new ones
*
* @param context the context to use.
* @param dso the DSpace object.
* @param metadataRest the rest representation of the new metadata.
* @throws SQLException if a database error occurs.
* @throws AuthorizeException if an authorization error occurs.
*/
public <T extends DSpaceObject> void mergeMetadata(Context context, T dso, MetadataRest metadataRest)
throws SQLException, AuthorizeException {
DSpaceObjectService<T> dsoService = contentServiceFactory.getDSpaceObjectService(dso);
for (Map.Entry<String, List<MetadataValueRest>> entry: metadataRest.getMap().entrySet()) {
List<MetadataValue> metadataByMetadataString = dsoService.getMetadataByMetadataString(dso, entry.getKey());
dsoService.removeMetadataValues(context, dso, metadataByMetadataString);
}
persistMetadataRest(context, dso, metadataRest, dsoService);
}
private <T extends DSpaceObject> void persistMetadataRest(Context context, T dso, MetadataRest metadataRest,
DSpaceObjectService<T> dsoService)
throws SQLException, AuthorizeException {
for (Map.Entry<String, List<MetadataValueRest>> entry: metadataRest.getMap().entrySet()) {
String[] seq = entry.getKey().split("\\.");
String schema = seq[0];
@@ -98,4 +141,5 @@ public class MetadataConverter implements DSpaceConverter<MetadataValueList, Met
}
dsoService.update(context, dso);
}
}

View File

@@ -245,7 +245,7 @@ public class CollectionRestRepository extends DSpaceObjectRestRepository<Collect
}
collection = cs.create(context, parent);
cs.update(context, collection);
metadataConverter.setMetadata(context, collection, collectionRest.getMetadata());
metadataConverter.mergeMetadata(context, collection, collectionRest.getMetadata());
} catch (SQLException e) {
throw new RuntimeException("Unable to create new Collection under parent Community " + id, e);
}

View File

@@ -91,25 +91,9 @@ public class CommunityRestRepository extends DSpaceObjectRestRepository<Communit
@Override
@PreAuthorize("hasAuthority('ADMIN')")
protected CommunityRest createAndReturn(Context context) throws AuthorizeException {
HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest();
ObjectMapper mapper = new ObjectMapper();
CommunityRest communityRest;
try {
ServletInputStream input = req.getInputStream();
communityRest = mapper.readValue(input, CommunityRest.class);
} catch (IOException e1) {
throw new UnprocessableEntityException("Error parsing request body: " + e1.toString());
}
Community community;
try {
// top-level community
community = cs.create(null, context);
cs.update(context, community);
metadataConverter.setMetadata(context, community, communityRest.getMetadata());
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e);
}
// top-level community
Community community = createCommunity(context, null);
return converter.toRest(community, utils.obtainProjection());
}
@@ -123,6 +107,24 @@ public class CommunityRestRepository extends DSpaceObjectRestRepository<Communit
"Cannot create a SubCommunity without providing a parent Community.");
}
Community parent;
try {
parent = cs.find(context, id);
if (parent == null) {
throw new UnprocessableEntityException("Parent community for id: "
+ id + " not found");
}
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e);
}
// sub-community
Community community = createCommunity(context, parent);
return converter.toRest(community, utils.obtainProjection());
}
private Community createCommunity(Context context, Community parent) throws AuthorizeException {
HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest();
ObjectMapper mapper = new ObjectMapper();
CommunityRest communityRest;
@@ -134,21 +136,15 @@ public class CommunityRestRepository extends DSpaceObjectRestRepository<Communit
}
Community community;
try {
Community parent = cs.find(context, id);
if (parent == null) {
throw new UnprocessableEntityException("Parent community for id: "
+ id + " not found");
}
// sub-community
community = cs.create(parent, context);
cs.update(context, community);
metadataConverter.setMetadata(context, community, communityRest.getMetadata());
metadataConverter.mergeMetadata(context, community, communityRest.getMetadata());
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e);
}
return converter.toRest(community, utils.obtainProjection());
return community;
}
@Override

View File

@@ -11,6 +11,8 @@ import static com.jayway.jsonpath.JsonPath.read;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata;
import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadataDoesNotExist;
import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadataNotEmpty;
import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadataStringEndsWith;
import static org.dspace.core.Constants.WRITE;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
@@ -1074,6 +1076,7 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes
AtomicReference<UUID> idRef = new AtomicReference<>();
AtomicReference<UUID> idRefNoEmbeds = new AtomicReference<>();
AtomicReference<String> handle = new AtomicReference<>();
try {
ObjectMapper mapper = new ObjectMapper();
@@ -1121,6 +1124,15 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes
MetadataMatcher.matchMetadata("dc.title",
"Title Text")
)))))
// capture "handle" returned in JSON response and check against the metadata
.andDo(result -> handle.set(
read(result.getResponse().getContentAsString(), "$.handle")))
.andExpect(jsonPath("$",
hasJsonPath("$.metadata", Matchers.allOf(
matchMetadataNotEmpty("dc.identifier.uri"),
matchMetadataStringEndsWith("dc.identifier.uri", handle.get())
)
)))
.andDo(result -> idRef
.set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))));;

View File

@@ -11,6 +11,8 @@ import static com.jayway.jsonpath.JsonPath.read;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static junit.framework.TestCase.assertEquals;
import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata;
import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadataNotEmpty;
import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadataStringEndsWith;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
@@ -117,6 +119,8 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest
// Capture the UUID of the created Community (see andDo() below)
AtomicReference<UUID> idRef = new AtomicReference<>();
AtomicReference<UUID> idRefNoEmbeds = new AtomicReference<>();
AtomicReference<String> handle = new AtomicReference<>();
try {
getClient(authToken).perform(post("/api/core/communities")
.content(mapper.writeValueAsBytes(comm))
@@ -136,15 +140,26 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest
hasJsonPath("$._links.subcommunities.href", not(empty())),
hasJsonPath("$._links.self.href", not(empty())),
hasJsonPath("$.metadata", Matchers.allOf(
matchMetadata("dc.description", "<p>Some cool HTML code here</p>"),
matchMetadata("dc.description.abstract",
"Sample top-level community created via the REST API"),
matchMetadata("dc.description.tableofcontents", "<p>HTML News</p>"),
matchMetadata("dc.rights", "Custom Copyright Text"),
matchMetadata("dc.title", "Title Text")
matchMetadata("dc.description", "<p>Some cool HTML code here</p>"),
matchMetadata("dc.description.abstract",
"Sample top-level community created via the REST API"),
matchMetadata("dc.description.tableofcontents", "<p>HTML News</p>"),
matchMetadata("dc.rights", "Custom Copyright Text"),
matchMetadata("dc.title", "Title Text")
)
)
)
)))
// capture "handle" returned in JSON response and check against the metadata
.andDo(result -> handle.set(
read(result.getResponse().getContentAsString(), "$.handle")))
.andExpect(jsonPath("$",
hasJsonPath("$.metadata", Matchers.allOf(
matchMetadataNotEmpty("dc.identifier.uri"),
matchMetadataStringEndsWith("dc.identifier.uri", handle.get())
)
)))
// capture "id" returned in JSON response
.andDo(result -> idRef
.set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))));
@@ -233,8 +248,9 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest
.put("dc.title",
new MetadataValueRest("Title Text")));
// Capture the UUID of the created Community (see andDo() below)
// Capture the UUID and Handle of the created Community (see andDo() below)
AtomicReference<UUID> idRef = new AtomicReference<>();
AtomicReference<String> handle = new AtomicReference<>();
try {
getClient(authToken).perform(post("/api/core/communities")
.content(mapper.writeValueAsBytes(comm))
@@ -253,17 +269,26 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest
hasJsonPath("$._links.subcommunities.href", not(empty())),
hasJsonPath("$._links.self.href", not(empty())),
hasJsonPath("$.metadata", Matchers.allOf(
MetadataMatcher.matchMetadata("dc.description",
"<p>Some cool HTML code here</p>"),
MetadataMatcher.matchMetadata("dc.description.abstract",
"Sample top-level community created via the REST API"),
MetadataMatcher.matchMetadata("dc.description.tableofcontents",
"<p>HTML News</p>"),
MetadataMatcher.matchMetadata("dc.rights",
"Custom Copyright Text"),
MetadataMatcher.matchMetadata("dc.title",
"Title Text")
MetadataMatcher.matchMetadata("dc.description",
"<p>Some cool HTML code here</p>"),
MetadataMatcher.matchMetadata("dc.description.abstract",
"Sample top-level community created via the REST API"),
MetadataMatcher.matchMetadata("dc.description.tableofcontents",
"<p>HTML News</p>"),
MetadataMatcher.matchMetadata("dc.rights",
"Custom Copyright Text"),
MetadataMatcher.matchMetadata("dc.title",
"Title Text")
)
)
)))
// capture "handle" returned in JSON response and check against the metadata
.andDo(result -> handle.set(
read(result.getResponse().getContentAsString(), "$.handle")))
.andExpect(jsonPath("$",
hasJsonPath("$.metadata", Matchers.allOf(
matchMetadataNotEmpty("dc.identifier.uri"),
matchMetadataStringEndsWith("dc.identifier.uri", handle.get())
)
)))
// capture "id" returned in JSON response

View File

@@ -10,6 +10,7 @@ package org.dspace.app.rest;
import static com.jayway.jsonpath.JsonPath.read;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata;
import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadataDoesNotExist;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.empty;
@@ -133,7 +134,8 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
hasJsonPath("$._links.self.href", not(empty())),
hasJsonPath("$.metadata", Matchers.allOf(
matchMetadata("eperson.firstname", "John"),
matchMetadata("eperson.lastname", "Doe")
matchMetadata("eperson.lastname", "Doe"),
matchMetadataDoesNotExist("dc.identifier.uri")
)))))
.andDo(result -> idRef
.set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id"))));

View File

@@ -9,10 +9,13 @@ package org.dspace.app.rest.matcher;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasNoJsonPath;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import org.hamcrest.Matcher;
import org.hamcrest.core.StringEndsWith;
/**
* Utility class to provide convenient matchers for metadata.
@@ -33,6 +36,25 @@ public class MetadataMatcher {
return hasJsonPath("$.['" + key + "'][*].value", hasItem(value));
}
/**
* Gets a matcher to ensure a given key contains at least one value.
* @param key the metadata key.
* @return the matcher
*/
public static Matcher<? super Object> matchMetadataNotEmpty(String key) {
return hasJsonPath("$.['" + key + "']", not(empty()));
}
/**
* Gets a matcher to ensure that at least one value for a given metadata key endsWith the given subString.
* @param key the metadata key.
* @param subString the subString which the value must end with
* @return the matcher
*/
public static Matcher<? super Object> matchMetadataStringEndsWith(String key, String subString) {
return hasJsonPath("$.['" + key + "'][*].value", hasItem(StringEndsWith.endsWith(subString)));
}
/**
* Gets a matcher to ensure a given value is present at a specific position in the list of values for a given key.
*