Skip to content

Commit

Permalink
Support virtual metadata for authority-controlled fields
Browse files Browse the repository at this point in the history
  • Loading branch information
kshepherd committed Sep 26, 2024
1 parent 9d80ef3 commit cbe7429
Show file tree
Hide file tree
Showing 7 changed files with 316 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.content;

import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.dspace.core.Constants;

/**
* Implementation of {@link VirtualMetadataValue} specifically for authority-based virtual metadata.
* It is a more simple implementation than {@link RelationshipMetadataValue} as it doesn't have a way to calculate
* relative place (place is set during value creation based on number of other values in the field) and does not
* override {@link VirtualMetadataValue#getID()}
*
* @author Kim Shepherd
*/
public class AuthorityVirtualMetadataValue extends VirtualMetadataValue {

/**
* Use authority key (URI, UUID, etc), value, metadatafield ID, and place number to generate hash code
* @return unique hash code
*/
@Override
public int hashCode() {
return new HashCodeBuilder().append(getAuthority()
.substring(Constants.VIRTUAL_AUTHORITY_PREFIX.length()))
.append(getMetadataFieldId())
.append(getValue())
.append(getPlace()).hashCode();
}

}
99 changes: 98 additions & 1 deletion dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
Expand All @@ -30,6 +31,8 @@
import org.dspace.app.requestitem.RequestItem;
import org.dspace.app.requestitem.service.RequestItemService;
import org.dspace.app.util.AuthorizeUtil;
import org.dspace.authority.AuthorityValue;
import org.dspace.authority.service.AuthorityValueService;
import org.dspace.authorize.AuthorizeConfiguration;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.ResourcePolicy;
Expand All @@ -49,6 +52,8 @@
import org.dspace.content.service.MetadataSchemaService;
import org.dspace.content.service.RelationshipService;
import org.dspace.content.service.WorkspaceItemService;
import org.dspace.content.virtual.AuthorityVirtualMetadataConfiguration;
import org.dspace.content.virtual.AuthorityVirtualMetadataPopulator;
import org.dspace.content.virtual.VirtualMetadataPopulator;
import org.dspace.contentreport.QueryPredicate;
import org.dspace.core.Constants;
Expand Down Expand Up @@ -147,6 +152,11 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl<Item> implements It
@Autowired(required = true)
protected VirtualMetadataPopulator virtualMetadataPopulator;

@Autowired
protected AuthorityVirtualMetadataPopulator authorityVirtualMetadataPopulator;
@Autowired
protected AuthorityValueService authorityValueService;

@Autowired(required = true)
private RelationshipMetadataService relationshipMetadataService;

Expand Down Expand Up @@ -1764,13 +1774,19 @@ public List<MetadataValue> getMetadata(Item item, String schema, String element,
}
if (item.isModifiedMetadataCache()) {
log.debug("Called getMetadata for " + item.getID() + " with invalid cache");
//rebuild cache
// Rebuild cache
List<MetadataValue> dbMetadataValues = item.getMetadata();

List<MetadataValue> fullMetadataValueList = new LinkedList<>();
fullMetadataValueList.addAll(relationshipMetadataService.getRelationshipMetadata(item, true));
fullMetadataValueList.addAll(dbMetadataValues);

// Now get authority virtual metadata - note this requires the configuration property *and* the
// enableVirtualMetadata parameter to be true
if (configurationService.getBooleanProperty("authority.metadata.virtual", false)) {
fullMetadataValueList.addAll(getAuthorityVirtualMetadata(item, dbMetadataValues));
}

item.setCachedMetadata(MetadataValueComparators.sort(fullMetadataValueList));
}

Expand All @@ -1787,6 +1803,87 @@ public List<MetadataValue> getMetadata(Item item, String schema, String element,
return values;
}

private List<MetadataValue> getAuthorityVirtualMetadata(Item item, List<MetadataValue> dbMetadataValues) {
Map<String, HashMap<String, AuthorityVirtualMetadataConfiguration>> authorityVirtualMaps
= authorityVirtualMetadataPopulator.getMap();
List<MetadataValue> authorityMetadataValues = new LinkedList<>();
// Make a map of counts - we want to calc a real 'place'
Map<String, Integer> fieldCounts = new HashMap<>();
for (MetadataValue mv : dbMetadataValues) {
String mvfn = mv.getMetadataField().toString('.');
if (!fieldCounts.containsKey(mvfn)) {
fieldCounts.put(mvfn, 1);
} else {
fieldCounts.put(mvfn, fieldCounts.get(mvfn) + 1);
}
}
Context context = new Context();
try {
for (MetadataValue dbValue : dbMetadataValues) {
if (null != dbValue.getAuthority()) {
// Get field
MetadataField dbField = dbValue.getMetadataField();
// In the virtual authority metadata spring XML, the map keys are field names of
// authority-controlled fields
String dbFieldName = dbField.toString('.');
// Check if this field is mapped *and* is configured for authority control
if (authorityVirtualMaps.containsKey(dbFieldName)
&& metadataAuthorityService.isAuthorityControlled(dbFieldName) ) {
AuthorityValue authorityValue = authorityValueService.findByUID(dbValue.getAuthority());
if (authorityValue == null) {
continue;
}
// Get map from config
HashMap<String, AuthorityVirtualMetadataConfiguration> authorityVirtualFieldMap =
authorityVirtualMaps.get(dbField.toString('.'));
// Iterate mapped fields
for (String virtualFieldName : authorityVirtualFieldMap.keySet()) {
if (virtualFieldName.equals(dbFieldName)) {
// Special case - we don't want to override / add to the actual field associated with
// this authority
// even if it is mapped, right?
continue;
}
AuthorityVirtualMetadataConfiguration authorityVirtualMetadataConfiguration =
authorityVirtualFieldMap.get(virtualFieldName);
MetadataField virtualField = metadataFieldService.findByString(context,
virtualFieldName, '.');
if (virtualField != null) {
List<String> virtualAuthorityValues =
authorityVirtualMetadataConfiguration.getValues(authorityValue);
// Iterate virtual authority values and insert them as authority virtual metadata
for (String virtualAuthorityValue : virtualAuthorityValues) {
AuthorityVirtualMetadataValue metadataValue = new AuthorityVirtualMetadataValue();
metadataValue.setMetadataField(virtualField);
// There is no information in the source authority object to help determine the
// place so instead we use the running count to set an appropriate place at the
// end of the existing metadata values for this field
int place = (fieldCounts.getOrDefault(virtualFieldName, 0));
metadataValue.setPlace(place);
fieldCounts.put(virtualFieldName, ++place);
// In RelationVirtualMetadata, the authority uses the prefix and the relationship
// ID, but we will instead use the authority ID as the second component
metadataValue.setAuthority(Constants.VIRTUAL_AUTHORITY_PREFIX
+ authorityValue.getId());
metadataValue.setValue(virtualAuthorityValue);
metadataValue.setDSpaceObject(item);
authorityMetadataValues.add(metadataValue);
}
} else {
log.error("Could not find virtual field specified in authority" +
"virtual metadata config: {}", virtualFieldName);
}
}
}
}
}
} catch (SQLException e) {
log.error(e.getMessage());
throw new RuntimeException(e);
}
return authorityMetadataValues;
}

/**
* Supports moving metadata by adding the metadata value or updating the place of the relationship
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* whether these Values should be counted for place calculation on both the native MetadataValues and the
* Relationship's place attributes.
*/
public class RelationshipMetadataValue extends MetadataValue {
public class RelationshipMetadataValue extends VirtualMetadataValue {

/**
* This property determines whether this RelationshipMetadataValue should be used in place calculation or not
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.content;

/**
* A simple abstraction to allow {@link RelationshipMetadataValue} and {@link AuthorityVirtualMetadataValue}
* extend it while allowing reflection or instance checking for MetadataValue and VirtualMetadataValue
*
* @author Kim Shepherd
*/
public class VirtualMetadataValue extends MetadataValue {

/**
* This is a bit of a hack, much like {@link RelationshipMetadataValue} - the ID is not something that corresponds
* to a real metadata value, but instead the source object. Unfortunately metadata value and relationships both
* use integers and not Strings like authority values.
* Returning -1 guarantees that any erroneous calls to an AuthorityVirtualMetadataValue object will not send
* the caller off to a real, unrelated MetadataValue, so this default behaviour is implemented
*
* @return integerised authority key
*/
@Override
public Integer getID() {
return -1;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.content.virtual;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.dspace.authority.AuthorityValue;

/**
* Implements the {@link AuthorityVirtualMetadataConfiguration} interface to achieve the generation of Virtual
* metadata for authority values, in a similar way to the entity-driven {@link VirtualMetadataConfiguration}.
* The Collected bean will take all the Solr document values for the defined fields and create a list of
* virtual metadata fields for use by the {@link AuthorityVirtualMetadataPopulator}.
*
* @author Kim Shepherd
*
*/
public class AuthorityCollected implements AuthorityVirtualMetadataConfiguration {
/**
* The field names to retrieve from the Authority Solr document - note these do NOT have to match
* the field names of the resulting virtual metadata values - they are set as the key of the map that contains
* this map. (e.g. 'dcterms.spatial -> ['my_solr_spatial_field' -> [x, y, z], 'another_field' -> [x, y, z]])
*/
private List<String> fields;

/**
* Generic getter for the fields property
* @return The list of field names
*/
public List<String> getFields() {
return fields;
}

/**
* Generic setter for the fields property
* @param fields the list of field names
*/
public void setFields(List<String> fields) {
this.fields = fields;
}

/**
* Retrieve the values from the given AuthorityValue "otherMetadataMap" for each configured fields property
* and return as a list. The authority value is responsible for how to store and return this map.
*
* @param authorityValue The authority value from which to build the list of values
* @return The String values for each field configured
*/
public List<String> getValues(AuthorityValue authorityValue) {
List<String> resultValues = new LinkedList<>();
List<String> fieldsToRetrieve = this.getFields();
Map<String, List<String>> metadataMap = authorityValue.getOtherMetadataMap();
for (String field : fieldsToRetrieve) {
List<String> otherMetadata = metadataMap.get(field);
if (otherMetadata != null) {
resultValues.addAll(otherMetadata);
}
}
return resultValues;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.content.virtual;

import java.util.List;

import org.dspace.authority.AuthorityValue;

/**
* This interface describes beans to be used for the {@link AuthorityVirtualMetadataPopulator} implementation.
* It achieves virtual metadata that looks and acts identical to the entity-driven {@link VirtualMetadataConfiguration}
* but uses Authority values as the source for metadata values instead of a related DSpace Item
*
* Functionality like 'useNameVariants' and 'useForPlace' are not implemented as authority values typically don't
* have enough information to derive this.
*
* @author Kim Shepherd
*/
public interface AuthorityVirtualMetadataConfiguration {

/**
* Retrieve the values from the given AuthorityValue "otherMetadataMap" for each configured fields property
* and return as a list. The authority value is responsible for how to store and return this map.
*
* @param authorityValue The authority value from which to build the list of values
* @return The String values for each field configured
*/
List<String> getValues(AuthorityValue authorityValue);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* 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.virtual;

import java.util.HashMap;
import java.util.Map;

/**
* This class simply sets and gets maps of maps of string-{@link AuthorityVirtualMetadataConfiguration}s, where the key
* is an authority-controlled metadata field like dc.subject, and the key in the value map is the virtual field name
* to set / transform for a given set of values (e.g. dc.description, dcterms.spatial).
* It operates in a similar way to entity {@link VirtualMetadataPopulator}
*
* @author Kim Shepherd
*/
public class AuthorityVirtualMetadataPopulator {

/**
* The map of authority controlled field names, to a map of virtual metadata field names and configurations.
*/
private Map<String, HashMap<String, AuthorityVirtualMetadataConfiguration>> map;

/**
* Sets the map of authority controlled field names to a map of virtual metadata field names and configurations.
* The map is used for authority virtual metadata population.
*
* @param map the map of authority controlled field names to virtual metadata field names and configurations
*/
public void setMap(Map<String, HashMap<String, AuthorityVirtualMetadataConfiguration>> map) {
this.map = map;
}

/**
* Retrieves the map of authority controlled field names to a map of virtual metadata field names and configurations.
*
* @return the map of authority controlled field names to virtual metadata field names and configurations
*/
public Map<String, HashMap<String, AuthorityVirtualMetadataConfiguration>> getMap() {
return map;
}

}

0 comments on commit cbe7429

Please sign in to comment.