Merge pull request #2792 from 4Science/draft_vocabulary

Refactoring authority framework, value pairs and controlled vocabulary
This commit is contained in:
Tim Donohue
2020-09-11 09:41:49 -05:00
committed by GitHub
58 changed files with 3004 additions and 1224 deletions

View File

@@ -33,21 +33,62 @@ public class Choice {
*/ */
public String value = null; public String value = null;
/**
* A boolean representing if choice entry value can selected (usually true).
* Hierarchical authority can flag some choice as not selectable to force the
* use to choice a more detailed terms in the tree, such a leaf or a deeper
* branch
*/
public boolean selectable = true;
public Map<String, String> extras = new HashMap<String, String>(); public Map<String, String> extras = new HashMap<String, String>();
public Choice() { public Choice() {
} }
/**
* Minimal constructor for this data object. It assumes an empty map of extras
* information and a selected choice
*
* @param authority the authority key
* @param value the text value to store in the metadata
* @param label the value to display to the user
*/
public Choice(String authority, String value, String label) { public Choice(String authority, String value, String label) {
this.authority = authority; this.authority = authority;
this.value = value; this.value = value;
this.label = label; this.label = label;
} }
/**
* Constructor to quickly setup the data object for basic authorities. The choice is assumed to be selectable.
*
* @param authority the authority key
* @param value the text value to store in the metadata
* @param label the value to display to the user
* @param extras a key value map of extra information related to this choice
*/
public Choice(String authority, String label, String value, Map<String, String> extras) { public Choice(String authority, String label, String value, Map<String, String> extras) {
this.authority = authority; this.authority = authority;
this.label = label; this.label = label;
this.value = value; this.value = value;
this.extras = extras; this.extras = extras;
} }
/**
* Constructor for common need of Hierarchical authorities that want to
* explicitely set the selectable flag
*
* @param authority the authority key
* @param value the text value to store in the metadata
* @param label the value to display to the user
* @param selectable true if the choice can be selected, false if the a more
* accurate choice should be preferred
*/
public Choice(String authority, String label, String value, boolean selectable) {
this.authority = authority;
this.label = label;
this.value = value;
this.selectable = selectable;
}
} }

View File

@@ -7,7 +7,10 @@
*/ */
package org.dspace.content.authority; package org.dspace.content.authority;
import org.dspace.content.Collection; import java.util.HashMap;
import java.util.Map;
import org.dspace.core.NameAwarePlugin;
/** /**
* Plugin interface that supplies an authority control mechanism for * Plugin interface that supplies an authority control mechanism for
@@ -17,7 +20,7 @@ import org.dspace.content.Collection;
* @see ChoiceAuthorityServiceImpl * @see ChoiceAuthorityServiceImpl
* @see MetadataAuthorityServiceImpl * @see MetadataAuthorityServiceImpl
*/ */
public interface ChoiceAuthority { public interface ChoiceAuthority extends NameAwarePlugin {
/** /**
* Get all values from the authority that match the preferred value. * Get all values from the authority that match the preferred value.
* Note that the offering was entered by the user and may contain * Note that the offering was entered by the user and may contain
@@ -32,15 +35,13 @@ public interface ChoiceAuthority {
* defaultSelected index in the Choices instance to the choice, if any, * defaultSelected index in the Choices instance to the choice, if any,
* that matches the value. * that matches the value.
* *
* @param field being matched for
* @param text user's value to match * @param text user's value to match
* @param collection database ID of Collection for context (owner of Item)
* @param start choice at which to start, 0 is first. * @param start choice at which to start, 0 is first.
* @param limit maximum number of choices to return, 0 for no limit. * @param limit maximum number of choices to return, 0 for no limit.
* @param locale explicit localization key if available, or null * @param locale explicit localization key if available, or null
* @return a Choices object (never null). * @return a Choices object (never null).
*/ */
public Choices getMatches(String field, String text, Collection collection, int start, int limit, String locale); public Choices getMatches(String text, int start, int limit, String locale);
/** /**
* Get the single "best" match (if any) of a value in the authority * Get the single "best" match (if any) of a value in the authority
@@ -51,13 +52,11 @@ public interface ChoiceAuthority {
* This call is typically used in non-interactive metadata ingest * This call is typically used in non-interactive metadata ingest
* where there is no interactive agent to choose from among options. * where there is no interactive agent to choose from among options.
* *
* @param field being matched for
* @param text user's value to match * @param text user's value to match
* @param collection database ID of Collection for context (owner of Item)
* @param locale explicit localization key if available, or null * @param locale explicit localization key if available, or null
* @return a Choices object (never null) with 1 or 0 values. * @return a Choices object (never null) with 1 or 0 values.
*/ */
public Choices getBestMatch(String field, String text, Collection collection, String locale); public Choices getBestMatch(String text, String locale);
/** /**
* Get the canonical user-visible "label" (i.e. short descriptive text) * Get the canonical user-visible "label" (i.e. short descriptive text)
@@ -67,31 +66,97 @@ public interface ChoiceAuthority {
* This may get called many times while populating a Web page so it should * This may get called many times while populating a Web page so it should
* be implemented as efficiently as possible. * be implemented as efficiently as possible.
* *
* @param field being matched for
* @param key authority key known to this authority. * @param key authority key known to this authority.
* @param locale explicit localization key if available, or null * @param locale explicit localization key if available, or null
* @return descriptive label - should always return something, never null. * @return descriptive label - should always return something, never null.
*/ */
public String getLabel(String field, String key, String locale); public String getLabel(String key, String locale);
/**
* Get the canonical value to store for a key in the authority. Can be localized
* given the implicit or explicit locale specification.
*
* @param key authority key known to this authority.
* @param locale explicit localization key if available, or null
* @return value to store - should always return something, never null.
*/
default String getValue(String key, String locale) {
return getLabel(key, locale);
}
/**
* Get a map of additional information related to the specified key in the
* authority.
*
* @param key the key of the entry
* @param locale explicit localization key if available, or null
* @return a map of additional information related to the key
*/
default Map<String, String> getExtra(String key, String locale) {
return new HashMap<String, String>();
}
/**
* Return true for hierarchical authorities
*
* @return <code>true</code> if hierarchical, default <code>false</code>
*/
default boolean isHierarchical() { default boolean isHierarchical() {
return false; return false;
} }
/**
* Scrollable authorities allows the scroll of the entries without applying
* filter/query to the
* {@link #getMatches(String, String, Collection, int, int, String)}
*
* @return <code>true</code> if scrollable, default <code>false</code>
*/
default boolean isScrollable() { default boolean isScrollable() {
return false; return false;
} }
default boolean hasIdentifier() { /**
return true; * Hierarchical authority can provide an hint for the UI about how many levels
* preload to improve the UX. It provides a valid default for hierarchical
* authorities
*
* @return <code>0</code> if hierarchical, null otherwise
*/
default Integer getPreloadLevel() {
return isHierarchical() ? 0 : null;
} }
default public Choice getChoice(String fieldKey, String authKey, String locale) { /**
* Build the preferred choice associated with the authKey. The default
* implementation delegate the creato to the {@link #getLabel(String, String)}
* {@link #getValue(String, String)} and {@link #getExtra(String, String)}
* methods but can be directly overriden for better efficiency or special
* scenario
*
* @param authKey authority key known to this authority.
* @param locale explicit localization key if available, or null
* @return the preferred choice for this authKey and locale
*/
default public Choice getChoice(String authKey, String locale) {
Choice result = new Choice(); Choice result = new Choice();
result.authority = authKey; result.authority = authKey;
result.label = getLabel(fieldKey, authKey, locale); result.label = getLabel(authKey, locale);
result.value = getLabel(fieldKey, authKey, locale); result.value = getValue(authKey, locale);
result.extras.putAll(getExtra(authKey, locale));
return result; return result;
} }
/**
* Provide a recommendation to store the authority in the metadata value if
* available in the in the provided choice(s). Usually ChoiceAuthority should
* recommend that so the default is true and it only need to be implemented in
* the unusual scenario
*
* @return <code>true</code> if the authority provided in any choice of this
* authority should be stored in the metadata value
*/
default public boolean storeAuthorityInMetadata() {
return true;
}
} }

View File

@@ -7,10 +7,13 @@
*/ */
package org.dspace.content.authority; package org.dspace.content.authority;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -19,6 +22,9 @@ import org.dspace.app.util.DCInput;
import org.dspace.app.util.DCInputSet; import org.dspace.app.util.DCInputSet;
import org.dspace.app.util.DCInputsReader; import org.dspace.app.util.DCInputsReader;
import org.dspace.app.util.DCInputsReaderException; import org.dspace.app.util.DCInputsReaderException;
import org.dspace.app.util.SubmissionConfig;
import org.dspace.app.util.SubmissionConfigReader;
import org.dspace.app.util.SubmissionConfigReaderException;
import org.dspace.content.Collection; import org.dspace.content.Collection;
import org.dspace.content.MetadataValue; import org.dspace.content.MetadataValue;
import org.dspace.content.authority.service.ChoiceAuthorityService; import org.dspace.content.authority.service.ChoiceAuthorityService;
@@ -54,23 +60,37 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
// map of field key to authority plugin // map of field key to authority plugin
protected Map<String, ChoiceAuthority> controller = new HashMap<String, ChoiceAuthority>(); protected Map<String, ChoiceAuthority> controller = new HashMap<String, ChoiceAuthority>();
// map of field key, form definition to authority plugin
protected Map<String, Map<String, ChoiceAuthority>> controllerFormDefinitions =
new HashMap<String, Map<String, ChoiceAuthority>>();
// map of field key to presentation type // map of field key to presentation type
protected Map<String, String> presentation = new HashMap<String, String>(); protected Map<String, String> presentation = new HashMap<String, String>();
// map of field key to closed value // map of field key to closed value
protected Map<String, Boolean> closed = new HashMap<String, Boolean>(); protected Map<String, Boolean> closed = new HashMap<String, Boolean>();
// map of authority name to field key // flag to track the initialization status of the service
protected Map<String, String> authorities = new HashMap<String, String>(); private boolean initialized = false;
// map of authority name to field keys (the same authority can be configured over multiple metadata)
protected Map<String, List<String>> authorities = new HashMap<String, List<String>>();
// map of authority name to form definition and field keys
protected Map<String, Map<String, List<String>>> authoritiesFormDefinitions =
new HashMap<String, Map<String, List<String>>>();
// the item submission reader
private SubmissionConfigReader itemSubmissionConfigReader;
@Autowired(required = true) @Autowired(required = true)
protected ConfigurationService configurationService; protected ConfigurationService configurationService;
@Autowired(required = true) @Autowired(required = true)
protected PluginService pluginService; protected PluginService pluginService;
private final String CHOICES_PLUGIN_PREFIX = "choices.plugin."; final static String CHOICES_PLUGIN_PREFIX = "choices.plugin.";
private final String CHOICES_PRESENTATION_PREFIX = "choices.presentation."; final static String CHOICES_PRESENTATION_PREFIX = "choices.presentation.";
private final String CHOICES_CLOSED_PREFIX = "choices.closed."; final static String CHOICES_CLOSED_PREFIX = "choices.closed.";
protected ChoiceAuthorityServiceImpl() { protected ChoiceAuthorityServiceImpl() {
} }
@@ -96,10 +116,25 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
@Override @Override
public Set<String> getChoiceAuthoritiesNames() { public Set<String> getChoiceAuthoritiesNames() {
if (authorities.keySet().isEmpty()) { init();
Set<String> authoritiesNames = new HashSet<String>();
authoritiesNames.addAll(authorities.keySet());
authoritiesNames.addAll(authoritiesFormDefinitions.keySet());
return authoritiesNames;
}
private synchronized void init() {
if (!initialized) {
try {
itemSubmissionConfigReader = new SubmissionConfigReader();
} catch (SubmissionConfigReaderException e) {
// the system is in an illegal state as the submission definition is not valid
throw new IllegalStateException("Error reading the item submission configuration: " + e.getMessage(),
e);
}
loadChoiceAuthorityConfigurations(); loadChoiceAuthorityConfigurations();
initialized = true;
} }
return authorities.keySet();
} }
@Override @Override
@@ -112,59 +147,62 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
@Override @Override
public Choices getMatches(String fieldKey, String query, Collection collection, public Choices getMatches(String fieldKey, String query, Collection collection,
int start, int limit, String locale) { int start, int limit, String locale) {
ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey); ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection);
if (ma == null) { if (ma == null) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"No choices plugin was configured for field \"" + fieldKey "No choices plugin was configured for field \"" + fieldKey
+ "\"."); + "\", collection=" + collection.getID().toString() + ".");
} }
return ma.getMatches(fieldKey, query, collection, start, limit, locale); return ma.getMatches(query, start, limit, locale);
} }
@Override @Override
public Choices getMatches(String fieldKey, String query, Collection collection, int start, int limit, String locale, public Choices getMatches(String fieldKey, String query, Collection collection, int start, int limit, String locale,
boolean externalInput) { boolean externalInput) {
ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey); ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection);
if (ma == null) { if (ma == null) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"No choices plugin was configured for field \"" + fieldKey "No choices plugin was configured for field \"" + fieldKey
+ "\"."); + "\", collection=" + collection.getID().toString() + ".");
} }
if (externalInput && ma instanceof SolrAuthority) { if (externalInput && ma instanceof SolrAuthority) {
((SolrAuthority) ma).addExternalResultsInNextMatches(); ((SolrAuthority) ma).addExternalResultsInNextMatches();
} }
return ma.getMatches(fieldKey, query, collection, start, limit, locale); return ma.getMatches(query, start, limit, locale);
} }
@Override @Override
public Choices getBestMatch(String fieldKey, String query, Collection collection, public Choices getBestMatch(String fieldKey, String query, Collection collection,
String locale) { String locale) {
ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey); ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection);
if (ma == null) { if (ma == null) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"No choices plugin was configured for field \"" + fieldKey "No choices plugin was configured for field \"" + fieldKey
+ "\"."); + "\", collection=" + collection.getID().toString() + ".");
} }
return ma.getBestMatch(fieldKey, query, collection, locale); return ma.getBestMatch(query, locale);
} }
@Override @Override
public String getLabel(MetadataValue metadataValue, String locale) { public String getLabel(MetadataValue metadataValue, Collection collection, String locale) {
return getLabel(metadataValue.getMetadataField().toString(), metadataValue.getAuthority(), locale); return getLabel(metadataValue.getMetadataField().toString(), collection, metadataValue.getAuthority(), locale);
} }
@Override @Override
public String getLabel(String fieldKey, String authKey, String locale) { public String getLabel(String fieldKey, Collection collection, String authKey, String locale) {
ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey); ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection);
if (ma == null) { if (ma == null) {
throw new IllegalArgumentException("No choices plugin was configured for field \"" + fieldKey + "\"."); throw new IllegalArgumentException(
"No choices plugin was configured for field \"" + fieldKey
+ "\", collection=" + collection.getID().toString() + ".");
} }
return ma.getLabel(fieldKey, authKey, locale); return ma.getLabel(authKey, locale);
} }
@Override @Override
public boolean isChoicesConfigured(String fieldKey) { public boolean isChoicesConfigured(String fieldKey, Collection collection) {
return getChoiceAuthorityMap().containsKey(fieldKey); return getAuthorityByFieldKeyCollection(fieldKey, collection) != null;
} }
@Override @Override
@@ -178,8 +216,14 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
} }
@Override @Override
public List<String> getVariants(MetadataValue metadataValue) { public List<String> getVariants(MetadataValue metadataValue, Collection collection) {
ChoiceAuthority ma = getChoiceAuthorityMap().get(metadataValue.getMetadataField().toString()); String fieldKey = metadataValue.getMetadataField().toString();
ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection);
if (ma == null) {
throw new IllegalArgumentException(
"No choices plugin was configured for field \"" + fieldKey
+ "\", collection=" + collection.getID().toString() + ".");
}
if (ma instanceof AuthorityVariantsSupport) { if (ma instanceof AuthorityVariantsSupport) {
AuthorityVariantsSupport avs = (AuthorityVariantsSupport) ma; AuthorityVariantsSupport avs = (AuthorityVariantsSupport) ma;
return avs.getVariants(metadataValue.getAuthority(), metadataValue.getLanguage()); return avs.getVariants(metadataValue.getAuthority(), metadataValue.getLanguage());
@@ -189,42 +233,53 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
@Override @Override
public String getChoiceAuthorityName(String schema, String element, String qualifier) { public String getChoiceAuthorityName(String schema, String element, String qualifier, Collection collection) {
String makeFieldKey = makeFieldKey(schema, element, qualifier); init();
if (getChoiceAuthorityMap().containsKey(makeFieldKey)) { String fieldKey = makeFieldKey(schema, element, qualifier);
for (String key : this.authorities.keySet()) { // check if there is an authority configured for the metadata valid for all the collections
if (this.authorities.get(key).equals(makeFieldKey)) { if (controller.containsKey(fieldKey)) {
return key; for (Entry<String, List<String>> authority2md : authorities.entrySet()) {
if (authority2md.getValue().contains(fieldKey)) {
return authority2md.getKey();
}
}
} else if (collection != null && controllerFormDefinitions.containsKey(fieldKey)) {
// there is an authority configured for the metadata valid for some collections,
// check if it is the requested collection
Map<String, ChoiceAuthority> controllerFormDef = controllerFormDefinitions.get(fieldKey);
SubmissionConfig submissionConfig = itemSubmissionConfigReader
.getSubmissionConfigByCollection(collection.getHandle());
String submissionName = submissionConfig.getSubmissionName();
// check if the requested collection has a submission definition that use an authority for the metadata
if (controllerFormDef.containsKey(submissionName)) {
for (Entry<String, Map<String, List<String>>> authority2defs2md :
authoritiesFormDefinitions.entrySet()) {
List<String> mdByDefinition = authority2defs2md.getValue().get(submissionName);
if (mdByDefinition != null && mdByDefinition.contains(fieldKey)) {
return authority2defs2md.getKey();
}
} }
} }
} }
return configurationService.getProperty( return null;
CHOICES_PLUGIN_PREFIX + schema + "." + element + (qualifier != null ? "." + qualifier : ""));
} }
protected String makeFieldKey(String schema, String element, String qualifier) { protected String makeFieldKey(String schema, String element, String qualifier) {
return Utils.standardize(schema, element, qualifier, "_"); return Utils.standardize(schema, element, qualifier, "_");
} }
/**
* Return map of key to ChoiceAuthority plugin
*
* @return
*/
private Map<String, ChoiceAuthority> getChoiceAuthorityMap() {
// If empty, load from configuration
if (controller.isEmpty()) {
loadChoiceAuthorityConfigurations();
}
return controller;
}
@Override @Override
public void clearCache() { public void clearCache() {
controller.clear(); controller.clear();
authorities.clear(); authorities.clear();
presentation.clear();
closed.clear();
controllerFormDefinitions.clear();
authoritiesFormDefinitions.clear();
itemSubmissionConfigReader = null;
initialized = false;
} }
private void loadChoiceAuthorityConfigurations() { private void loadChoiceAuthorityConfigurations() {
// Get all configuration keys starting with a given prefix // Get all configuration keys starting with a given prefix
List<String> propKeys = configurationService.getPropertyKeys(CHOICES_PLUGIN_PREFIX); List<String> propKeys = configurationService.getPropertyKeys(CHOICES_PLUGIN_PREFIX);
@@ -249,71 +304,127 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
"Skipping invalid configuration for " + key + " because named plugin not found: " + authorityName); "Skipping invalid configuration for " + key + " because named plugin not found: " + authorityName);
continue; continue;
} }
if (!authorities.containsKey(authorityName)) {
controller.put(fkey, ma);
authorities.put(authorityName, fkey);
} else {
log.warn(
"Skipping invalid configuration for " + key + " because plugin is alredy in use: " +
authorityName + " used by " + authorities
.get(authorityName));
continue;
}
controller.put(fkey, ma);
List<String> fkeys;
if (authorities.containsKey(authorityName)) {
fkeys = authorities.get(authorityName);
} else {
fkeys = new ArrayList<String>();
}
fkeys.add(fkey);
authorities.put(authorityName, fkeys);
log.debug("Choice Control: For field=" + fkey + ", Plugin=" + ma); log.debug("Choice Control: For field=" + fkey + ", Plugin=" + ma);
} }
autoRegisterChoiceAuthorityFromInputReader(); autoRegisterChoiceAuthorityFromInputReader();
} }
/**
* This method will register all the authorities that are required due to the
* submission forms configuration. This includes authorities for value pairs and
* xml vocabularies
*/
private void autoRegisterChoiceAuthorityFromInputReader() { private void autoRegisterChoiceAuthorityFromInputReader() {
try { try {
List<SubmissionConfig> submissionConfigs = itemSubmissionConfigReader
.getAllSubmissionConfigs(Integer.MAX_VALUE, 0);
DCInputsReader dcInputsReader = new DCInputsReader(); DCInputsReader dcInputsReader = new DCInputsReader();
for (DCInputSet dcinputSet : dcInputsReader.getAllInputs(Integer.MAX_VALUE, 0)) {
DCInput[][] dcinputs = dcinputSet.getFields(); // loop over all the defined item submission configuration
for (DCInput[] dcrows : dcinputs) { for (SubmissionConfig subCfg : submissionConfigs) {
for (DCInput dcinput : dcrows) { String submissionName = subCfg.getSubmissionName();
if (StringUtils.isNotBlank(dcinput.getPairsType()) List<DCInputSet> inputsBySubmissionName = dcInputsReader.getInputsBySubmissionName(submissionName);
|| StringUtils.isNotBlank(dcinput.getVocabulary())) { // loop over the submission forms configuration eventually associated with the submission panel
String authorityName = dcinput.getPairsType(); for (DCInputSet dcinputSet : inputsBySubmissionName) {
if (StringUtils.isBlank(authorityName)) { DCInput[][] dcinputs = dcinputSet.getFields();
for (DCInput[] dcrows : dcinputs) {
for (DCInput dcinput : dcrows) {
// for each input in the form check if it is associated with a real value pairs
// or an xml vocabulary
String authorityName = null;
if (StringUtils.isNotBlank(dcinput.getPairsType())
&& !StringUtils.equals(dcinput.getInputType(), "qualdrop_value")) {
authorityName = dcinput.getPairsType();
} else if (StringUtils.isNotBlank(dcinput.getVocabulary())) {
authorityName = dcinput.getVocabulary(); authorityName = dcinput.getVocabulary();
} }
if (!StringUtils.equals(dcinput.getInputType(), "qualdrop_value")) {
// do we have an authority?
if (StringUtils.isNotBlank(authorityName)) {
String fieldKey = makeFieldKey(dcinput.getSchema(), dcinput.getElement(), String fieldKey = makeFieldKey(dcinput.getSchema(), dcinput.getElement(),
dcinput.getQualifier()); dcinput.getQualifier());
ChoiceAuthority ca = controller.get(authorityName); ChoiceAuthority ca = controller.get(authorityName);
if (ca == null) { if (ca == null) {
InputFormSelfRegisterWrapperAuthority ifa = new ca = (ChoiceAuthority) pluginService
InputFormSelfRegisterWrapperAuthority();
if (controller.containsKey(fieldKey)) {
ifa = (InputFormSelfRegisterWrapperAuthority) controller.get(fieldKey);
}
ChoiceAuthority ma = (ChoiceAuthority) pluginService
.getNamedPlugin(ChoiceAuthority.class, authorityName); .getNamedPlugin(ChoiceAuthority.class, authorityName);
if (ma == null) { if (ca == null) {
log.warn("Skipping invalid configuration for " + fieldKey throw new IllegalStateException("Invalid configuration for " + fieldKey
+ " because named plugin not found: " + authorityName); + " in submission definition " + submissionName
continue; + ", form definition " + dcinputSet.getFormName()
+ " no named plugin found: " + authorityName);
} }
ifa.getDelegates().put(dcinputSet.getFormName(), ma);
controller.put(fieldKey, ifa);
}
if (!authorities.containsKey(authorityName)) {
authorities.put(authorityName, fieldKey);
} }
addAuthorityToFormCacheMap(submissionName, fieldKey, ca);
addFormDetailsToAuthorityCacheMap(submissionName, authorityName, fieldKey);
} }
} }
} }
} }
} }
} catch (DCInputsReaderException e) { } catch (DCInputsReaderException e) {
throw new IllegalStateException(e.getMessage(), e); // the system is in an illegal state as the submission definition is not valid
throw new IllegalStateException("Error reading the item submission configuration: " + e.getMessage(),
e);
} }
} }
/**
* Add the form/field to the cache map keeping track of which form/field are
* associated with the specific authority name
*
* @param submissionName the form definition name
* @param authorityName the name of the authority plugin
* @param fieldKey the field key that use the authority
*/
private void addFormDetailsToAuthorityCacheMap(String submissionName, String authorityName, String fieldKey) {
Map<String, List<String>> submissionDefinitionNames2fieldKeys;
if (authoritiesFormDefinitions.containsKey(authorityName)) {
submissionDefinitionNames2fieldKeys = authoritiesFormDefinitions.get(authorityName);
} else {
submissionDefinitionNames2fieldKeys = new HashMap<String, List<String>>();
}
List<String> fields;
if (submissionDefinitionNames2fieldKeys.containsKey(submissionName)) {
fields = submissionDefinitionNames2fieldKeys.get(submissionName);
} else {
fields = new ArrayList<String>();
}
fields.add(fieldKey);
submissionDefinitionNames2fieldKeys.put(submissionName, fields);
authoritiesFormDefinitions.put(authorityName, submissionDefinitionNames2fieldKeys);
}
/**
* Add the authority plugin to the cache map keeping track of which authority is
* used by a specific form/field
*
* @param submissionName the submission definition name
* @param fieldKey the field key that require the authority
* @param ca the authority plugin
*/
private void addAuthorityToFormCacheMap(String submissionName, String fieldKey, ChoiceAuthority ca) {
Map<String, ChoiceAuthority> definition2authority;
if (controllerFormDefinitions.containsKey(fieldKey)) {
definition2authority = controllerFormDefinitions.get(fieldKey);
} else {
definition2authority = new HashMap<String, ChoiceAuthority>();
}
definition2authority.put(submissionName, ca);
controllerFormDefinitions.put(fieldKey, definition2authority);
}
/** /**
* Return map of key to presentation * Return map of key to presentation
* *
@@ -370,26 +481,6 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
return closed; return closed;
} }
@Override
public String getChoiceMetadatabyAuthorityName(String name) {
if (authorities.isEmpty()) {
loadChoiceAuthorityConfigurations();
}
if (authorities.containsKey(name)) {
return authorities.get(name);
}
return null;
}
@Override
public Choice getChoice(String fieldKey, String authKey, String locale) {
ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey);
if (ma == null) {
throw new IllegalArgumentException("No choices plugin was configured for field \"" + fieldKey + "\".");
}
return ma.getChoice(fieldKey, authKey, locale);
}
@Override @Override
public ChoiceAuthority getChoiceAuthorityByAuthorityName(String authorityName) { public ChoiceAuthority getChoiceAuthorityByAuthorityName(String authorityName) {
ChoiceAuthority ma = (ChoiceAuthority) ChoiceAuthority ma = (ChoiceAuthority)
@@ -401,4 +492,68 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
} }
return ma; return ma;
} }
private ChoiceAuthority getAuthorityByFieldKeyCollection(String fieldKey, Collection collection) {
init();
ChoiceAuthority ma = controller.get(fieldKey);
if (ma == null && collection != null) {
SubmissionConfigReader configReader;
try {
configReader = new SubmissionConfigReader();
SubmissionConfig submissionName = configReader.getSubmissionConfigByCollection(collection.getHandle());
ma = controllerFormDefinitions.get(fieldKey).get(submissionName.getSubmissionName());
} catch (SubmissionConfigReaderException e) {
// the system is in an illegal state as the submission definition is not valid
throw new IllegalStateException("Error reading the item submission configuration: " + e.getMessage(),
e);
}
}
return ma;
}
@Override
public boolean storeAuthority(String fieldKey, Collection collection) {
// currently only named authority can eventually provide real authority
return controller.containsKey(fieldKey);
}
/**
* Wrapper that calls getChoicesByParent method of the plugin.
*
* @param authorityName authority name
* @param parentId parent Id
* @param start choice at which to start, 0 is first.
* @param limit maximum number of choices to return, 0 for no limit.
* @param locale explicit localization key if available, or null
* @return a Choices object (never null).
* @see org.dspace.content.authority.ChoiceAuthority#getChoicesByParent(java.lang.String, java.lang.String,
* int, int, java.lang.String)
*/
@Override
public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale) {
HierarchicalAuthority ma = (HierarchicalAuthority) getChoiceAuthorityByAuthorityName(authorityName);
return ma.getChoicesByParent(authorityName, parentId, start, limit, locale);
}
/**
* Wrapper that calls getTopChoices method of the plugin.
*
* @param authorityName authority name
* @param start choice at which to start, 0 is first.
* @param limit maximum number of choices to return, 0 for no limit.
* @param locale explicit localization key if available, or null
* @return a Choices object (never null).
* @see org.dspace.content.authority.ChoiceAuthority#getTopChoices(java.lang.String, int, int, java.lang.String)
*/
@Override
public Choices getTopChoices(String authorityName, int start, int limit, String locale) {
HierarchicalAuthority ma = (HierarchicalAuthority) getChoiceAuthorityByAuthorityName(authorityName);
return ma.getTopChoices(authorityName, start, limit, locale);
}
@Override
public Choice getParentChoice(String authorityName, String vocabularyId, String locale) {
HierarchicalAuthority ma = (HierarchicalAuthority) getChoiceAuthorityByAuthorityName(authorityName);
return ma.getParentChoice(authorityName, vocabularyId, locale);
}
} }

View File

@@ -9,14 +9,20 @@ package org.dspace.content.authority;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.dspace.app.util.DCInputsReader; import org.dspace.app.util.DCInputsReader;
import org.dspace.app.util.DCInputsReaderException; import org.dspace.app.util.DCInputsReaderException;
import org.dspace.content.Collection; import org.dspace.core.I18nUtil;
import org.dspace.core.SelfNamedPlugin; import org.dspace.core.SelfNamedPlugin;
/** /**
@@ -44,16 +50,38 @@ import org.dspace.core.SelfNamedPlugin;
public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority { public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority {
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(DCInputAuthority.class); private static Logger log = org.apache.logging.log4j.LogManager.getLogger(DCInputAuthority.class);
private String values[] = null; /**
private String labels[] = null; * The map of the values available for a specific language. Examples of keys are
* "en", "it", "uk"
*/
private Map<String, String[]> values = null;
private static DCInputsReader dci = null; /**
* The map of the labels available for a specific language. Examples of keys are
* "en", "it", "uk"
*/
private Map<String, String[]> labels = null;
/**
* The map of the input form reader associated to use for a specific java locale
*/
private static Map<Locale, DCInputsReader> dcis = null;
private static String pluginNames[] = null; private static String pluginNames[] = null;
public DCInputAuthority() { public DCInputAuthority() {
super(); super();
} }
@Override
public boolean storeAuthorityInMetadata() {
// For backward compatibility value pairs don't store authority in
// the metadatavalue
return false;
}
public static void reset() {
pluginNames = null;
}
public static String[] getPluginNames() { public static String[] getPluginNames() {
if (pluginNames == null) { if (pluginNames == null) {
initPluginNames(); initPluginNames();
@@ -63,20 +91,28 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority
} }
private static synchronized void initPluginNames() { private static synchronized void initPluginNames() {
Locale[] locales = I18nUtil.getSupportedLocales();
Set<String> names = new HashSet<String>();
if (pluginNames == null) { if (pluginNames == null) {
try { try {
if (dci == null) { dcis = new HashMap<Locale, DCInputsReader>();
dci = new DCInputsReader(); for (Locale locale : locales) {
dcis.put(locale, new DCInputsReader(I18nUtil.getInputFormsFileName(locale)));
}
for (Locale l : locales) {
Iterator pi = dcis.get(l).getPairsNameIterator();
while (pi.hasNext()) {
names.add((String) pi.next());
}
}
DCInputsReader dcirDefault = new DCInputsReader();
Iterator pi = dcirDefault.getPairsNameIterator();
while (pi.hasNext()) {
names.add((String) pi.next());
} }
} catch (DCInputsReaderException e) { } catch (DCInputsReaderException e) {
log.error("Failed reading DCInputs initialization: ", e); log.error("Failed reading DCInputs initialization: ", e);
} }
List<String> names = new ArrayList<String>();
Iterator pi = dci.getPairsNameIterator();
while (pi.hasNext()) {
names.add((String) pi.next());
}
pluginNames = names.toArray(new String[names.size()]); pluginNames = names.toArray(new String[names.size()]);
log.debug("Got plugin names = " + Arrays.deepToString(pluginNames)); log.debug("Got plugin names = " + Arrays.deepToString(pluginNames));
} }
@@ -85,45 +121,65 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority
// once-only load of values and labels // once-only load of values and labels
private void init() { private void init() {
if (values == null) { if (values == null) {
values = new HashMap<String, String[]>();
labels = new HashMap<String, String[]>();
String pname = this.getPluginInstanceName(); String pname = this.getPluginInstanceName();
List<String> pairs = dci.getPairs(pname); for (Locale l : dcis.keySet()) {
if (pairs != null) { DCInputsReader dci = dcis.get(l);
values = new String[pairs.size() / 2]; List<String> pairs = dci.getPairs(pname);
labels = new String[pairs.size() / 2]; if (pairs != null) {
for (int i = 0; i < pairs.size(); i += 2) { String[] valuesLocale = new String[pairs.size() / 2];
labels[i / 2] = pairs.get(i); String[]labelsLocale = new String[pairs.size() / 2];
values[i / 2] = pairs.get(i + 1); for (int i = 0; i < pairs.size(); i += 2) {
labelsLocale[i / 2] = pairs.get(i);
valuesLocale[i / 2] = pairs.get(i + 1);
}
values.put(l.getLanguage(), valuesLocale);
labels.put(l.getLanguage(), labelsLocale);
log.debug("Found pairs for name=" + pname + ",locale=" + l);
} else {
log.error("Failed to find any pairs for name=" + pname, new IllegalStateException());
} }
log.debug("Found pairs for name=" + pname);
} else {
log.error("Failed to find any pairs for name=" + pname, new IllegalStateException());
} }
} }
} }
@Override @Override
public Choices getMatches(String field, String query, Collection collection, int start, int limit, String locale) { public Choices getMatches(String query, int start, int limit, String locale) {
init(); init();
Locale currentLocale = I18nUtil.getSupportedLocale(locale);
String[] valuesLocale = values.get(currentLocale.getLanguage());
String[] labelsLocale = labels.get(currentLocale.getLanguage());
int dflt = -1; int dflt = -1;
Choice v[] = new Choice[values.length]; int found = 0;
for (int i = 0; i < values.length; ++i) { List<Choice> v = new ArrayList<Choice>();
v[i] = new Choice(values[i], values[i], labels[i]); for (int i = 0; i < valuesLocale.length; ++i) {
if (values[i].equalsIgnoreCase(query)) { if (query == null || StringUtils.containsIgnoreCase(valuesLocale[i], query)) {
dflt = i; if (found >= start && v.size() < limit) {
v.add(new Choice(null, valuesLocale[i], labelsLocale[i]));
if (valuesLocale[i].equalsIgnoreCase(query)) {
dflt = i;
}
}
found++;
} }
} }
return new Choices(v, 0, v.length, Choices.CF_AMBIGUOUS, false, dflt); Choice[] vArray = new Choice[v.size()];
return new Choices(v.toArray(vArray), start, found, Choices.CF_AMBIGUOUS, false, dflt);
} }
@Override @Override
public Choices getBestMatch(String field, String text, Collection collection, String locale) { public Choices getBestMatch(String text, String locale) {
init(); init();
for (int i = 0; i < values.length; ++i) { Locale currentLocale = I18nUtil.getSupportedLocale(locale);
if (text.equalsIgnoreCase(values[i])) { String[] valuesLocale = values.get(currentLocale.getLanguage());
String[] labelsLocale = labels.get(currentLocale.getLanguage());
for (int i = 0; i < valuesLocale.length; ++i) {
if (text.equalsIgnoreCase(valuesLocale[i])) {
Choice v[] = new Choice[1]; Choice v[] = new Choice[1];
v[0] = new Choice(String.valueOf(i), values[i], labels[i]); v[0] = new Choice(String.valueOf(i), valuesLocale[i], labelsLocale[i]);
return new Choices(v, 0, v.length, Choices.CF_UNCERTAIN, false, 0); return new Choices(v, 0, v.length, Choices.CF_UNCERTAIN, false, 0);
} }
} }
@@ -131,19 +187,31 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority
} }
@Override @Override
public String getLabel(String field, String key, String locale) { public String getLabel(String key, String locale) {
init(); init();
// Get default if locale is empty
if (StringUtils.isBlank(locale)) {
locale = I18nUtil.getDefaultLocale().getLanguage();
}
String[] labelsLocale = labels.get(locale);
int pos = -1; int pos = -1;
for (int i = 0; i < values.length; i++) { for (int i = 0; i < labelsLocale.length; i++) {
if (values[i].equals(key)) { if (labelsLocale[i].equals(key)) {
pos = i; pos = i;
break; break;
} }
} }
if (pos != -1) { if (pos != -1) {
return labels[pos]; return labelsLocale[pos];
} else { } else {
return "UNKNOWN KEY " + key; return "UNKNOWN KEY " + key;
} }
} }
@Override
public boolean isScrollable() {
return true;
}
} }

View File

@@ -10,7 +10,9 @@ package org.dspace.content.authority;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import javax.xml.xpath.XPath; import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathExpressionException;
@@ -19,7 +21,6 @@ import javax.xml.xpath.XPathFactory;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.dspace.content.Collection;
import org.dspace.core.SelfNamedPlugin; import org.dspace.core.SelfNamedPlugin;
import org.dspace.services.ConfigurationService; import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.services.factory.DSpaceServicesFactory;
@@ -54,25 +55,35 @@ import org.xml.sax.InputSource;
* @author Michael B. Klein * @author Michael B. Klein
*/ */
public class DSpaceControlledVocabulary extends SelfNamedPlugin implements ChoiceAuthority { public class DSpaceControlledVocabulary extends SelfNamedPlugin implements HierarchicalAuthority {
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(DSpaceControlledVocabulary.class); private static Logger log = org.apache.logging.log4j.LogManager.getLogger(DSpaceControlledVocabulary.class);
protected static String xpathTemplate = "//node[contains(translate(@label,'ABCDEFGHIJKLMNOPQRSTUVWXYZ'," + protected static String xpathTemplate = "//node[contains(translate(@label,'ABCDEFGHIJKLMNOPQRSTUVWXYZ'," +
"'abcdefghijklmnopqrstuvwxyz'),'%s')]"; "'abcdefghijklmnopqrstuvwxyz'),'%s')]";
protected static String idTemplate = "//node[@id = '%s']"; protected static String idTemplate = "//node[@id = '%s']";
protected static String idParentTemplate = "//node[@id = '%s']/parent::isComposedBy"; protected static String labelTemplate = "//node[@label = '%s']";
protected static String idParentTemplate = "//node[@id = '%s']/parent::isComposedBy/parent::node";
protected static String rootTemplate = "/node";
protected static String pluginNames[] = null; protected static String pluginNames[] = null;
protected String vocabularyName = null; protected String vocabularyName = null;
protected InputSource vocabulary = null; protected InputSource vocabulary = null;
protected Boolean suggestHierarchy = true; protected Boolean suggestHierarchy = false;
protected Boolean storeHierarchy = true; protected Boolean storeHierarchy = true;
protected String hierarchyDelimiter = "::"; protected String hierarchyDelimiter = "::";
protected Integer preloadLevel = 1;
public DSpaceControlledVocabulary() { public DSpaceControlledVocabulary() {
super(); super();
} }
@Override
public boolean storeAuthorityInMetadata() {
// For backward compatibility controlled vocabularies don't store the node id in
// the metadatavalue
return false;
}
public static String[] getPluginNames() { public static String[] getPluginNames() {
if (pluginNames == null) { if (pluginNames == null) {
initPluginNames(); initPluginNames();
@@ -112,6 +123,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic
String configurationPrefix = "vocabulary.plugin." + vocabularyName; String configurationPrefix = "vocabulary.plugin." + vocabularyName;
storeHierarchy = config.getBooleanProperty(configurationPrefix + ".hierarchy.store", storeHierarchy); storeHierarchy = config.getBooleanProperty(configurationPrefix + ".hierarchy.store", storeHierarchy);
suggestHierarchy = config.getBooleanProperty(configurationPrefix + ".hierarchy.suggest", suggestHierarchy); suggestHierarchy = config.getBooleanProperty(configurationPrefix + ".hierarchy.suggest", suggestHierarchy);
preloadLevel = config.getIntProperty(configurationPrefix + ".hierarchy.preloadLevel", preloadLevel);
String configuredDelimiter = config.getProperty(configurationPrefix + ".delimiter"); String configuredDelimiter = config.getProperty(configurationPrefix + ".delimiter");
if (configuredDelimiter != null) { if (configuredDelimiter != null) {
hierarchyDelimiter = configuredDelimiter.replaceAll("(^\"|\"$)", ""); hierarchyDelimiter = configuredDelimiter.replaceAll("(^\"|\"$)", "");
@@ -142,7 +154,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic
} }
@Override @Override
public Choices getMatches(String field, String text, Collection collection, int start, int limit, String locale) { public Choices getMatches(String text, int start, int limit, String locale) {
init(); init();
log.debug("Getting matches for '" + text + "'"); log.debug("Getting matches for '" + text + "'");
String xpathExpression = ""; String xpathExpression = "";
@@ -151,59 +163,60 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic
xpathExpression += String.format(xpathTemplate, textHierarchy[i].replaceAll("'", "&apos;").toLowerCase()); xpathExpression += String.format(xpathTemplate, textHierarchy[i].replaceAll("'", "&apos;").toLowerCase());
} }
XPath xpath = XPathFactory.newInstance().newXPath(); XPath xpath = XPathFactory.newInstance().newXPath();
Choice[] choices; int total = 0;
List<Choice> choices = new ArrayList<Choice>();
try { try {
NodeList results = (NodeList) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET); NodeList results = (NodeList) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET);
String[] authorities = new String[results.getLength()]; total = results.getLength();
String[] values = new String[results.getLength()]; choices = getChoicesFromNodeList(results, start, limit);
String[] labels = new String[results.getLength()];
String[] parent = new String[results.getLength()];
String[] notes = new String[results.getLength()];
for (int i = 0; i < results.getLength(); i++) {
Node node = results.item(i);
readNode(authorities, values, labels, parent, notes, i, node);
}
int resultCount = labels.length - start;
// limit = 0 means no limit
if ((limit > 0) && (resultCount > limit)) {
resultCount = limit;
}
choices = new Choice[resultCount];
if (resultCount > 0) {
for (int i = 0; i < resultCount; i++) {
choices[i] = new Choice(authorities[start + i], values[start + i], labels[start + i]);
if (StringUtils.isNotBlank(parent[i])) {
choices[i].extras.put("parent", parent[i]);
}
if (StringUtils.isNotBlank(notes[i])) {
choices[i].extras.put("note", notes[i]);
}
}
}
} catch (XPathExpressionException e) { } catch (XPathExpressionException e) {
choices = new Choice[0]; log.warn(e.getMessage(), e);
return new Choices(true);
} }
return new Choices(choices, 0, choices.length, Choices.CF_AMBIGUOUS, false); return new Choices(choices.toArray(new Choice[choices.size()]), start, total, Choices.CF_AMBIGUOUS,
total > start + limit);
} }
@Override @Override
public Choices getBestMatch(String field, String text, Collection collection, String locale) { public Choices getBestMatch(String text, String locale) {
init(); init();
log.debug("Getting best match for '" + text + "'"); log.debug("Getting best matches for '" + text + "'");
return getMatches(field, text, collection, 0, 2, locale); String xpathExpression = "";
} String[] textHierarchy = text.split(hierarchyDelimiter, -1);
for (int i = 0; i < textHierarchy.length; i++) {
@Override xpathExpression += String.format(labelTemplate, textHierarchy[i].replaceAll("'", "&apos;"));
public String getLabel(String field, String key, String locale) { }
init();
String xpathExpression = String.format(idTemplate, key);
XPath xpath = XPathFactory.newInstance().newXPath(); XPath xpath = XPathFactory.newInstance().newXPath();
List<Choice> choices = new ArrayList<Choice>();
try { try {
Node node = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE); NodeList results = (NodeList) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET);
return node.getAttributes().getNamedItem("label").getNodeValue(); choices = getChoicesFromNodeList(results, 0, 1);
} catch (XPathExpressionException e) { } catch (XPathExpressionException e) {
return (""); log.warn(e.getMessage(), e);
return new Choices(true);
} }
return new Choices(choices.toArray(new Choice[choices.size()]), 0, choices.size(), Choices.CF_AMBIGUOUS, false);
}
@Override
public String getLabel(String key, String locale) {
return getNodeLabel(key, this.suggestHierarchy);
}
@Override
public String getValue(String key, String locale) {
return getNodeLabel(key, this.storeHierarchy);
}
@Override
public Choice getChoice(String authKey, String locale) {
Node node;
try {
node = getNode(authKey);
} catch (XPathExpressionException e) {
return null;
}
return createChoiceFromNode(node);
} }
@Override @Override
@@ -212,81 +225,227 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic
} }
@Override @Override
public Choice getChoice(String fieldKey, String authKey, String locale) { public Choices getTopChoices(String authorityName, int start, int limit, String locale) {
init(); init();
log.debug("Getting matches for '" + authKey + "'"); String xpathExpression = rootTemplate;
String xpathExpression = String.format(idTemplate, authKey); return getChoicesByXpath(xpathExpression, start, limit);
XPath xpath = XPathFactory.newInstance().newXPath();
try {
Node node = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE);
if (node != null) {
String[] authorities = new String[1];
String[] values = new String[1];
String[] labels = new String[1];
String[] parent = new String[1];
String[] note = new String[1];
readNode(authorities, values, labels, parent, note, 0, node);
if (values.length > 0) {
Choice choice = new Choice(authorities[0], values[0], labels[0]);
if (StringUtils.isNotBlank(parent[0])) {
choice.extras.put("parent", parent[0]);
}
if (StringUtils.isNotBlank(note[0])) {
choice.extras.put("note", note[0]);
}
return choice;
}
}
} catch (XPathExpressionException e) {
log.warn(e.getMessage(), e);
}
return null;
} }
private void readNode(String[] authorities, String[] values, String[] labels, String[] parent, String[] notes, @Override
int i, Node node) { public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale) {
init();
String xpathExpression = String.format(idTemplate, parentId);
return getChoicesByXpath(xpathExpression, start, limit);
}
@Override
public Choice getParentChoice(String authorityName, String childId, String locale) {
init();
try {
String xpathExpression = String.format(idParentTemplate, childId);
Choice choice = createChoiceFromNode(getNodeFromXPath(xpathExpression));
return choice;
} catch (XPathExpressionException e) {
log.error(e.getMessage(), e);
return null;
}
}
@Override
public Integer getPreloadLevel() {
return preloadLevel;
}
private boolean isRootElement(Node node) {
if (node != null && node.getOwnerDocument().getDocumentElement().equals(node)) {
return true;
}
return false;
}
private Node getNode(String key) throws XPathExpressionException {
init();
String xpathExpression = String.format(idTemplate, key);
Node node = getNodeFromXPath(xpathExpression);
return node;
}
private Node getNodeFromXPath(String xpathExpression) throws XPathExpressionException {
XPath xpath = XPathFactory.newInstance().newXPath();
Node node = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE);
return node;
}
private List<Choice> getChoicesFromNodeList(NodeList results, int start, int limit) {
List<Choice> choices = new ArrayList<Choice>();
for (int i = 0; i < results.getLength(); i++) {
if (i < start) {
continue;
}
if (choices.size() == limit) {
break;
}
Node node = results.item(i);
Choice choice = new Choice(getAuthority(node), getLabel(node), getValue(node),
isSelectable(node));
choice.extras = addOtherInformation(getParent(node), getNote(node), getChildren(node), getAuthority(node));
choices.add(choice);
}
return choices;
}
private Map<String, String> addOtherInformation(String parentCurr, String noteCurr,
List<String> childrenCurr, String authorityCurr) {
Map<String, String> extras = new HashMap<String, String>();
if (StringUtils.isNotBlank(parentCurr)) {
extras.put("parent", parentCurr);
}
if (StringUtils.isNotBlank(noteCurr)) {
extras.put("note", noteCurr);
}
if (childrenCurr.size() > 0) {
extras.put("hasChildren", "true");
} else {
extras.put("hasChildren", "false");
}
extras.put("id", authorityCurr);
return extras;
}
private String getNodeLabel(String key, boolean useHierarchy) {
try {
Node node = getNode(key);
if (useHierarchy) {
return this.buildString(node);
} else {
return node.getAttributes().getNamedItem("label").getNodeValue();
}
} catch (XPathExpressionException e) {
return ("");
}
}
private String getLabel(Node node) {
String hierarchy = this.buildString(node); String hierarchy = this.buildString(node);
if (this.suggestHierarchy) { if (this.suggestHierarchy) {
labels[i] = hierarchy; return hierarchy;
} else { } else {
labels[i] = node.getAttributes().getNamedItem("label").getNodeValue(); return node.getAttributes().getNamedItem("label").getNodeValue();
}
if (this.storeHierarchy) {
values[i] = hierarchy;
} else {
values[i] = node.getAttributes().getNamedItem("label").getNodeValue();
} }
}
private String getValue(Node node) {
String hierarchy = this.buildString(node);
if (this.storeHierarchy) {
return hierarchy;
} else {
return node.getAttributes().getNamedItem("label").getNodeValue();
}
}
private String getNote(Node node) {
NodeList childNodes = node.getChildNodes(); NodeList childNodes = node.getChildNodes();
for (int ci = 0; ci < childNodes.getLength(); ci++) { for (int ci = 0; ci < childNodes.getLength(); ci++) {
Node firstChild = childNodes.item(ci); Node firstChild = childNodes.item(ci);
if (firstChild != null && "hasNote".equals(firstChild.getNodeName())) { if (firstChild != null && "hasNote".equals(firstChild.getNodeName())) {
String nodeValue = firstChild.getTextContent(); String nodeValue = firstChild.getTextContent();
if (StringUtils.isNotBlank(nodeValue)) { if (StringUtils.isNotBlank(nodeValue)) {
notes[i] = nodeValue; return nodeValue;
} }
} }
} }
Node idAttr = node.getAttributes().getNamedItem("id"); return null;
if (null != idAttr) { // 'id' is optional }
authorities[i] = idAttr.getNodeValue();
if (isHierarchical()) { private List<String> getChildren(Node node) {
Node parentN = node.getParentNode(); List<String> children = new ArrayList<String>();
if (parentN != null) { NodeList childNodes = node.getChildNodes();
parentN = parentN.getParentNode(); for (int ci = 0; ci < childNodes.getLength(); ci++) {
if (parentN != null) { Node firstChild = childNodes.item(ci);
Node parentIdAttr = parentN.getAttributes().getNamedItem("id"); if (firstChild != null && "isComposedBy".equals(firstChild.getNodeName())) {
if (null != parentIdAttr) { for (int cii = 0; cii < firstChild.getChildNodes().getLength(); cii++) {
parent[i] = parentIdAttr.getNodeValue(); Node childN = firstChild.getChildNodes().item(cii);
if (childN != null && "node".equals(childN.getNodeName())) {
Node childIdAttr = childN.getAttributes().getNamedItem("id");
if (null != childIdAttr) {
children.add(childIdAttr.getNodeValue());
} }
} }
} }
break;
} }
} else { }
authorities[i] = null; return children;
parent[i] = null; }
private boolean isSelectable(Node node) {
Node selectableAttr = node.getAttributes().getNamedItem("selectable");
if (null != selectableAttr) {
return Boolean.valueOf(selectableAttr.getNodeValue());
} else { // Default is true
return true;
} }
} }
private String getParent(Node node) {
Node parentN = node.getParentNode();
if (parentN != null) {
parentN = parentN.getParentNode();
if (parentN != null && !isRootElement(parentN)) {
return buildString(parentN);
}
}
return null;
}
private String getAuthority(Node node) {
Node idAttr = node.getAttributes().getNamedItem("id");
if (null != idAttr) { // 'id' is optional
return idAttr.getNodeValue();
} else {
return null;
}
}
private Choices getChoicesByXpath(String xpathExpression, int start, int limit) {
List<Choice> choices = new ArrayList<Choice>();
XPath xpath = XPathFactory.newInstance().newXPath();
try {
Node parentNode = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE);
int count = 0;
if (parentNode != null) {
NodeList childNodes = (NodeList) xpath.evaluate(".//isComposedBy", parentNode, XPathConstants.NODE);
if (null != childNodes) {
for (int i = 0; i < childNodes.getLength(); i++) {
Node childNode = childNodes.item(i);
if (childNode != null && "node".equals(childNode.getNodeName())) {
if (count < start || choices.size() >= limit) {
count++;
continue;
}
count++;
choices.add(createChoiceFromNode(childNode));
}
}
}
return new Choices(choices.toArray(new Choice[choices.size()]), start, count,
Choices.CF_AMBIGUOUS, false);
}
} catch (XPathExpressionException e) {
log.warn(e.getMessage(), e);
return new Choices(true);
}
return new Choices(false);
}
private Choice createChoiceFromNode(Node node) {
if (node != null && !isRootElement(node)) {
Choice choice = new Choice(getAuthority(node), getLabel(node), getValue(node),
isSelectable(node));
choice.extras = addOtherInformation(getParent(node), getNote(node),getChildren(node), getAuthority(node));
return choice;
}
return null;
}
} }

View File

@@ -0,0 +1,85 @@
/**
* 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.authority;
/**
* Plugin interface that supplies an authority control mechanism for
* one metadata field.
*
* @author Larry Stone
* @see ChoiceAuthority
*/
public interface HierarchicalAuthority extends ChoiceAuthority {
/**
* Get all values from the authority that match the preferred value.
* Note that the offering was entered by the user and may contain
* mixed/incorrect case, whitespace, etc so the plugin should be careful
* to clean up user data before making comparisons.
*
* Value of a "Name" field will be in canonical DSpace person name format,
* which is "Lastname, Firstname(s)", e.g. "Smith, John Q.".
*
* Some authorities with a small set of values may simply return the whole
* set for any sample value, although it's a good idea to set the
* defaultSelected index in the Choices instance to the choice, if any,
* that matches the value.
*
* @param authorityName authority name
* @param start choice at which to start, 0 is first.
* @param limit maximum number of choices to return, 0 for no limit.
* @param locale explicit localization key if available, or null
* @return a Choices object (never null).
*/
public Choices getTopChoices(String authorityName, int start, int limit, String locale);
/**
* Get all values from the authority that match the preferred value.
* Note that the offering was entered by the user and may contain
* mixed/incorrect case, whitespace, etc so the plugin should be careful
* to clean up user data before making comparisons.
*
* Value of a "Name" field will be in canonical DSpace person name format,
* which is "Lastname, Firstname(s)", e.g. "Smith, John Q.".
*
* Some authorities with a small set of values may simply return the whole
* set for any sample value, although it's a good idea to set the
* defaultSelected index in the Choices instance to the choice, if any,
* that matches the value.
*
* @param authorityName authority name
* @param parentId user's value to match
* @param start choice at which to start, 0 is first.
* @param limit maximum number of choices to return, 0 for no limit.
* @param locale explicit localization key if available, or null
* @return a Choices object (never null).
*/
public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale);
/**
* It returns the parent choice in the hierarchy if any
*
* @param authorityName authority name
* @param vocabularyId user's value to match
* @param locale explicit localization key if available, or null
* @return a Choice object
*/
public Choice getParentChoice(String authorityName, String vocabularyId, String locale);
/**
* Provides an hint for the UI to preload some levels to improve the UX. It
* usually mean that these preloaded level will be shown expanded by default
*/
public Integer getPreloadLevel();
@Override
default boolean isHierarchical() {
return true;
}
}

View File

@@ -1,166 +0,0 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.content.authority;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.app.util.DCInputsReader;
import org.dspace.app.util.DCInputsReaderException;
import org.dspace.content.Collection;
/**
* This authority is registered automatically by the ChoiceAuthorityService for
* all the metadata that use a value-pair or a vocabulary in the submission-form.xml
*
* It keeps a map of form-name vs ChoiceAuthority to delegate the execution of
* the method to the specific ChoiceAuthority configured for the collection when
* the same metadata have different vocabulary or value-pair on a collection
* basis
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
*/
public class InputFormSelfRegisterWrapperAuthority implements ChoiceAuthority {
private static Logger log =
org.apache.logging.log4j.LogManager.getLogger(InputFormSelfRegisterWrapperAuthority.class);
private Map<String, ChoiceAuthority> delegates = new HashMap<String, ChoiceAuthority>();
private static DCInputsReader dci = null;
private void init() {
try {
if (dci == null) {
dci = new DCInputsReader();
}
} catch (DCInputsReaderException e) {
log.error("Failed reading DCInputs initialization: ", e);
}
}
@Override
public Choices getMatches(String field, String query, Collection collection, int start, int limit, String locale) {
String formName;
try {
init();
if (collection == null) {
Set<Choice> choices = new HashSet<Choice>();
//workaround search in all authority configured
for (ChoiceAuthority ca : delegates.values()) {
Choices tmp = ca.getMatches(field, query, null, start, limit, locale);
if (tmp.total > 0) {
Set<Choice> mySet = new HashSet<Choice>(Arrays.asList(tmp.values));
choices.addAll(mySet);
}
}
if (!choices.isEmpty()) {
Choice[] results = new Choice[choices.size()];
choices.toArray(results);
return new Choices(results, 0, choices.size(), Choices.CF_AMBIGUOUS, false);
}
} else {
formName = dci.getInputFormNameByCollectionAndField(collection, field);
return delegates.get(formName).getMatches(field, query, collection, start, limit, locale);
}
} catch (DCInputsReaderException e) {
log.error(e.getMessage(), e);
}
return new Choices(Choices.CF_NOTFOUND);
}
@Override
public Choices getBestMatch(String field, String text, Collection collection, String locale) {
String formName;
try {
init();
if (collection == null) {
Set<Choice> choices = new HashSet<Choice>();
//workaround search in all authority configured
for (ChoiceAuthority ca : delegates.values()) {
Choices tmp = ca.getBestMatch(field, text, null, locale);
if (tmp.total > 0) {
Set<Choice> mySet = new HashSet<Choice>(Arrays.asList(tmp.values));
choices.addAll(mySet);
}
}
if (!choices.isEmpty()) {
Choice[] results = new Choice[choices.size() - 1];
choices.toArray(results);
return new Choices(results, 0, choices.size(), Choices.CF_UNCERTAIN, false);
}
} else {
formName = dci.getInputFormNameByCollectionAndField(collection, field);
return delegates.get(formName).getBestMatch(field, text, collection, locale);
}
} catch (DCInputsReaderException e) {
log.error(e.getMessage(), e);
}
return new Choices(Choices.CF_NOTFOUND);
}
@Override
public String getLabel(String field, String key, String locale) {
// TODO we need to manage REALLY the authority
// WRONG BEHAVIOUR: now in each delegates can exists the same key with
// different value
for (ChoiceAuthority delegate : delegates.values()) {
String label = delegate.getLabel(field, key, locale);
if (StringUtils.isNotBlank(label)) {
return label;
}
}
return "UNKNOWN KEY " + key;
}
@Override
public boolean isHierarchical() {
// TODO we need to manage REALLY the authority
// WRONG BEHAVIOUR: now in each delegates can exists the same key with
// different value
for (ChoiceAuthority delegate : delegates.values()) {
return delegate.isHierarchical();
}
return false;
}
@Override
public boolean isScrollable() {
// TODO we need to manage REALLY the authority
// WRONG BEHAVIOUR: now in each delegates can exists the same key with
// different value
for (ChoiceAuthority delegate : delegates.values()) {
return delegate.isScrollable();
}
return false;
}
@Override
public boolean hasIdentifier() {
// TODO we need to manage REALLY the authority
// WRONG BEHAVIOUR: now in each delegates can exists the same key with
// different value
for (ChoiceAuthority delegate : delegates.values()) {
return delegate.hasIdentifier();
}
return false;
}
public Map<String, ChoiceAuthority> getDelegates() {
return delegates;
}
public void setDelegates(Map<String, ChoiceAuthority> delegates) {
this.delegates = delegates;
}
}

View File

@@ -14,12 +14,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.dspace.app.util.DCInput;
import org.dspace.app.util.DCInputSet;
import org.dspace.app.util.DCInputsReader;
import org.dspace.app.util.DCInputsReaderException;
import org.dspace.content.MetadataField; import org.dspace.content.MetadataField;
import org.dspace.content.authority.service.MetadataAuthorityService; import org.dspace.content.authority.service.MetadataAuthorityService;
import org.dspace.content.service.MetadataFieldService; import org.dspace.content.service.MetadataFieldService;
@@ -144,8 +139,6 @@ public class MetadataAuthorityServiceImpl implements MetadataAuthorityService {
if (dmc >= Choices.CF_UNSET) { if (dmc >= Choices.CF_UNSET) {
defaultMinConfidence = dmc; defaultMinConfidence = dmc;
} }
autoRegisterAuthorityFromInputReader();
} }
} }
@@ -205,7 +198,6 @@ public class MetadataAuthorityServiceImpl implements MetadataAuthorityService {
} }
} }
/** /**
* Give the minimal level of confidence required to consider valid an authority value * Give the minimal level of confidence required to consider valid an authority value
* for the given metadata. * for the given metadata.
@@ -229,35 +221,4 @@ public class MetadataAuthorityServiceImpl implements MetadataAuthorityService {
} }
return copy; return copy;
} }
private void autoRegisterAuthorityFromInputReader() {
try {
DCInputsReader dcInputsReader = new DCInputsReader();
for (DCInputSet dcinputSet : dcInputsReader.getAllInputs(Integer.MAX_VALUE, 0)) {
DCInput[][] dcinputs = dcinputSet.getFields();
for (DCInput[] dcrows : dcinputs) {
for (DCInput dcinput : dcrows) {
if (StringUtils.isNotBlank(dcinput.getPairsType())
|| StringUtils.isNotBlank(dcinput.getVocabulary())) {
String authorityName = dcinput.getPairsType();
if (StringUtils.isBlank(authorityName)) {
authorityName = dcinput.getVocabulary();
}
if (!StringUtils.equals(dcinput.getInputType(), "qualdrop_value")) {
String fieldKey = makeFieldKey(dcinput.getSchema(), dcinput.getElement(),
dcinput.getQualifier());
boolean req = ConfigurationManager
.getBooleanProperty("authority.required." + fieldKey, false);
controlled.put(fieldKey, true);
isAuthorityRequired.put(fieldKey, req);
}
}
}
}
}
} catch (DCInputsReaderException e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
} }

View File

@@ -7,13 +7,13 @@
*/ */
package org.dspace.content.authority; package org.dspace.content.authority;
import org.dspace.content.Collection;
/** /**
* This is a *very* stupid test fixture for authority control, and also * This is a *very* stupid test fixture for authority control, and also
* serves as a trivial example of an authority plugin implementation. * serves as a trivial example of an authority plugin implementation.
*/ */
public class SampleAuthority implements ChoiceAuthority { public class SampleAuthority implements ChoiceAuthority {
private String pluginInstanceName;
protected static String values[] = { protected static String values[] = {
"sun", "sun",
"mon", "mon",
@@ -35,7 +35,7 @@ public class SampleAuthority implements ChoiceAuthority {
}; };
@Override @Override
public Choices getMatches(String field, String query, Collection collection, int start, int limit, String locale) { public Choices getMatches(String query, int start, int limit, String locale) {
int dflt = -1; int dflt = -1;
Choice v[] = new Choice[values.length]; Choice v[] = new Choice[values.length];
for (int i = 0; i < values.length; ++i) { for (int i = 0; i < values.length; ++i) {
@@ -48,7 +48,7 @@ public class SampleAuthority implements ChoiceAuthority {
} }
@Override @Override
public Choices getBestMatch(String field, String text, Collection collection, String locale) { public Choices getBestMatch(String text, String locale) {
for (int i = 0; i < values.length; ++i) { for (int i = 0; i < values.length; ++i) {
if (text.equalsIgnoreCase(values[i])) { if (text.equalsIgnoreCase(values[i])) {
Choice v[] = new Choice[1]; Choice v[] = new Choice[1];
@@ -60,7 +60,17 @@ public class SampleAuthority implements ChoiceAuthority {
} }
@Override @Override
public String getLabel(String field, String key, String locale) { public String getLabel(String key, String locale) {
return labels[Integer.parseInt(key)]; return labels[Integer.parseInt(key)];
} }
@Override
public String getPluginInstanceName() {
return pluginInstanceName;
}
@Override
public void setPluginInstanceName(String name) {
this.pluginInstanceName = name;
}
} }

View File

@@ -11,6 +11,7 @@ import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@@ -24,8 +25,9 @@ import org.dspace.authority.AuthorityValue;
import org.dspace.authority.SolrAuthorityInterface; import org.dspace.authority.SolrAuthorityInterface;
import org.dspace.authority.factory.AuthorityServiceFactory; import org.dspace.authority.factory.AuthorityServiceFactory;
import org.dspace.authority.service.AuthorityValueService; import org.dspace.authority.service.AuthorityValueService;
import org.dspace.content.Collection;
import org.dspace.core.ConfigurationManager; import org.dspace.core.ConfigurationManager;
import org.dspace.core.NameAwarePlugin;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.services.factory.DSpaceServicesFactory;
/** /**
@@ -35,7 +37,14 @@ import org.dspace.services.factory.DSpaceServicesFactory;
* @author Mark Diggory (markd at atmire dot com) * @author Mark Diggory (markd at atmire dot com)
*/ */
public class SolrAuthority implements ChoiceAuthority { public class SolrAuthority implements ChoiceAuthority {
/** the name assigned to the specific instance by the PluginService, @see {@link NameAwarePlugin} **/
private String authorityName;
/**
* the metadata managed by the plugin instance, derived from its authority name
* in the form schema_element_qualifier
*/
private String field;
protected SolrAuthorityInterface source = protected SolrAuthorityInterface source =
DSpaceServicesFactory.getInstance().getServiceManager() DSpaceServicesFactory.getInstance().getServiceManager()
.getServiceByName("AuthoritySource", SolrAuthorityInterface.class); .getServiceByName("AuthoritySource", SolrAuthorityInterface.class);
@@ -45,8 +54,9 @@ public class SolrAuthority implements ChoiceAuthority {
protected boolean externalResults = false; protected boolean externalResults = false;
protected final AuthorityValueService authorityValueService = AuthorityServiceFactory.getInstance() protected final AuthorityValueService authorityValueService = AuthorityServiceFactory.getInstance()
.getAuthorityValueService(); .getAuthorityValueService();
protected final ConfigurationService configurationService = DSpaceServicesFactory.getInstance()
public Choices getMatches(String field, String text, Collection collection, int start, int limit, String locale, .getConfigurationService();
public Choices getMatches(String text, int start, int limit, String locale,
boolean bestMatch) { boolean bestMatch) {
if (limit == 0) { if (limit == 0) {
limit = 10; limit = 10;
@@ -193,13 +203,13 @@ public class SolrAuthority implements ChoiceAuthority {
} }
@Override @Override
public Choices getMatches(String field, String text, Collection collection, int start, int limit, String locale) { public Choices getMatches(String text, int start, int limit, String locale) {
return getMatches(field, text, collection, start, limit, locale, true); return getMatches(text, start, limit, locale, true);
} }
@Override @Override
public Choices getBestMatch(String field, String text, Collection collection, String locale) { public Choices getBestMatch(String text, String locale) {
Choices matches = getMatches(field, text, collection, 0, 1, locale, false); Choices matches = getMatches(text, 0, 1, locale, false);
if (matches.values.length != 0 && !matches.values[0].value.equalsIgnoreCase(text)) { if (matches.values.length != 0 && !matches.values[0].value.equalsIgnoreCase(text)) {
matches = new Choices(false); matches = new Choices(false);
} }
@@ -207,7 +217,7 @@ public class SolrAuthority implements ChoiceAuthority {
} }
@Override @Override
public String getLabel(String field, String key, String locale) { public String getLabel(String key, String locale) {
try { try {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("requesting label for key " + key + " using locale " + locale); log.debug("requesting label for key " + key + " using locale " + locale);
@@ -276,4 +286,23 @@ public class SolrAuthority implements ChoiceAuthority {
public void addExternalResultsInNextMatches() { public void addExternalResultsInNextMatches() {
this.externalResults = true; this.externalResults = true;
} }
@Override
public void setPluginInstanceName(String name) {
authorityName = name;
for (Entry conf : configurationService.getProperties().entrySet()) {
if (StringUtils.startsWith((String) conf.getKey(), ChoiceAuthorityServiceImpl.CHOICES_PLUGIN_PREFIX)
&& StringUtils.equals((String) conf.getValue(), authorityName)) {
field = ((String) conf.getKey()).substring(ChoiceAuthorityServiceImpl.CHOICES_PLUGIN_PREFIX.length())
.replace(".", "_");
// exit the look immediately as we have found it
break;
}
}
}
@Override
public String getPluginInstanceName() {
return authorityName;
}
} }

View File

@@ -11,7 +11,6 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.dspace.content.Collection;
/** /**
* This is a *very* stupid test fixture for authority control with AuthorityVariantsSupport. * This is a *very* stupid test fixture for authority control with AuthorityVariantsSupport.
@@ -19,6 +18,7 @@ import org.dspace.content.Collection;
* @author Andrea Bollini (CILEA) * @author Andrea Bollini (CILEA)
*/ */
public class TestAuthority implements ChoiceAuthority, AuthorityVariantsSupport { public class TestAuthority implements ChoiceAuthority, AuthorityVariantsSupport {
private String pluginInstanceName;
@Override @Override
public List<String> getVariants(String key, String locale) { public List<String> getVariants(String key, String locale) {
@@ -33,8 +33,7 @@ public class TestAuthority implements ChoiceAuthority, AuthorityVariantsSupport
} }
@Override @Override
public Choices getMatches(String field, String text, Collection collection, public Choices getMatches(String text, int start, int limit, String locale) {
int start, int limit, String locale) {
Choices choices = new Choices(false); Choices choices = new Choices(false);
if (StringUtils.isNotBlank(text)) { if (StringUtils.isNotBlank(text)) {
@@ -52,8 +51,7 @@ public class TestAuthority implements ChoiceAuthority, AuthorityVariantsSupport
} }
@Override @Override
public Choices getBestMatch(String field, String text, Collection collection, public Choices getBestMatch(String text, String locale) {
String locale) {
Choices choices = new Choices(false); Choices choices = new Choices(false);
if (StringUtils.isNotBlank(text)) { if (StringUtils.isNotBlank(text)) {
@@ -70,10 +68,20 @@ public class TestAuthority implements ChoiceAuthority, AuthorityVariantsSupport
} }
@Override @Override
public String getLabel(String field, String key, String locale) { public String getLabel(String key, String locale) {
if (StringUtils.isNotBlank(key)) { if (StringUtils.isNotBlank(key)) {
return key.replaceAll("authority", "label"); return key.replaceAll("authority", "label");
} }
return "Unknown"; return "Unknown";
} }
@Override
public String getPluginInstanceName() {
return pluginInstanceName;
}
@Override
public void setPluginInstanceName(String name) {
this.pluginInstanceName = name;
}
} }

View File

@@ -48,10 +48,10 @@ public interface ChoiceAuthorityService {
* @param element element of metadata field * @param element element of metadata field
* @param qualifier qualifier of metadata field * @param qualifier qualifier of metadata field
* @return the name of the choice authority associated with the specified * @return the name of the choice authority associated with the specified
* metadata. Throw IllegalArgumentException if the supplied metadat * metadata. Throw IllegalArgumentException if the supplied metadata
* is not associated with an authority choice * is not associated with an authority choice
*/ */
public String getChoiceAuthorityName(String schema, String element, String qualifier); public String getChoiceAuthorityName(String schema, String element, String qualifier, Collection collection);
/** /**
* Wrapper that calls getMatches method of the plugin corresponding to * Wrapper that calls getMatches method of the plugin corresponding to
@@ -112,30 +112,33 @@ public interface ChoiceAuthorityService {
* the metadata field defined by schema,element,qualifier. * the metadata field defined by schema,element,qualifier.
* *
* @param metadataValue metadata value * @param metadataValue metadata value
* @param collection Collection owner of Item
* @param locale explicit localization key if available * @param locale explicit localization key if available
* @return label * @return label
*/ */
public String getLabel(MetadataValue metadataValue, String locale); public String getLabel(MetadataValue metadataValue, Collection collection, String locale);
/** /**
* Wrapper that calls getLabel method of the plugin corresponding to * Wrapper that calls getLabel method of the plugin corresponding to
* the metadata field defined by single field key. * the metadata field defined by single field key.
* *
* @param fieldKey single string identifying metadata field * @param fieldKey single string identifying metadata field
* @param collection Collection owner of Item
* @param locale explicit localization key if available * @param locale explicit localization key if available
* @param authKey authority key * @param authKey authority key
* @return label * @return label
*/ */
public String getLabel(String fieldKey, String authKey, String locale); public String getLabel(String fieldKey, Collection collection, String authKey, String locale);
/** /**
* Predicate, is there a Choices configuration of any kind for the * Predicate, is there a Choices configuration of any kind for the
* given metadata field? * given metadata field?
* *
* @param fieldKey single string identifying metadata field * @param fieldKey single string identifying metadata field
* @param collection Collection owner of Item
* @return true if choices are configured for this field. * @return true if choices are configured for this field.
*/ */
public boolean isChoicesConfigured(String fieldKey); public boolean isChoicesConfigured(String fieldKey, Collection collection);
/** /**
* Get the presentation keyword (should be "lookup", "select" or "suggest", but this * Get the presentation keyword (should be "lookup", "select" or "suggest", but this
@@ -160,12 +163,14 @@ public interface ChoiceAuthorityService {
* @param metadataValue metadata value * @param metadataValue metadata value
* @return List of variants * @return List of variants
*/ */
public List<String> getVariants(MetadataValue metadataValue); public List<String> getVariants(MetadataValue metadataValue, Collection collection);
public String getChoiceMetadatabyAuthorityName(String name);
public Choice getChoice(String fieldKey, String authKey, String locale);
/**
* Return the ChoiceAuthority instance identified by the specified name
*
* @param authorityName the ChoiceAuthority instance name
* @return the ChoiceAuthority identified by the specified name
*/
public ChoiceAuthority getChoiceAuthorityByAuthorityName(String authorityName); public ChoiceAuthority getChoiceAuthorityByAuthorityName(String authorityName);
/** /**
@@ -173,4 +178,49 @@ public interface ChoiceAuthorityService {
*/ */
public void clearCache(); public void clearCache();
/**
* Should we store the authority key (if any) for such field key and collection?
*
* @param fieldKey single string identifying metadata field
* @param collection Collection owner of Item or where the item is submitted to
* @return true if the configuration allows to store the authority value
*/
public boolean storeAuthority(String fieldKey, Collection collection);
/**
* Wrapper that calls getChoicesByParent method of the plugin.
*
* @param authorityName authority name
* @param parentId parent Id
* @param start choice at which to start, 0 is first.
* @param limit maximum number of choices to return, 0 for no limit.
* @param locale explicit localization key if available, or null
* @return a Choices object (never null).
* @see org.dspace.content.authority.ChoiceAuthority#getChoicesByParent(java.lang.String, java.lang.String,
* int, int, java.lang.String)
*/
public Choices getChoicesByParent(String authorityName, String parentId, int start, int limit, String locale);
/**
* Wrapper that calls getTopChoices method of the plugin.
*
* @param authorityName authority name
* @param start choice at which to start, 0 is first.
* @param limit maximum number of choices to return, 0 for no limit.
* @param locale explicit localization key if available, or null
* @return a Choices object (never null).
* @see org.dspace.content.authority.ChoiceAuthority#getTopChoices(java.lang.String, int, int, java.lang.String)
*/
public Choices getTopChoices(String authorityName, int start, int limit, String locale);
/**
* Return the direct parent of an entry identified by its id in an hierarchical
* authority.
*
* @param authorityName authority name
* @param vocabularyId child id
* @param locale explicit localization key if available, or null
* @return the parent Choice object if any
*/
public Choice getParentChoice(String authorityName, String vocabularyId, String locale);
} }

View File

@@ -191,6 +191,23 @@ public class I18nUtil {
return supportedLocale; return supportedLocale;
} }
/**
* Gets the appropriate supported Locale according for a given Locale If
* no appropriate supported locale is found, the DEFAULTLOCALE is used
*
* @param locale String to find the corresponding Locale
* @return supportedLocale
* Locale for session according to locales supported by this DSpace instance as set in dspace.cfg
*/
public static Locale getSupportedLocale(String locale) {
Locale currentLocale = null;
if (locale != null) {
currentLocale = I18nUtil.getSupportedLocale(new Locale(locale));
} else {
currentLocale = I18nUtil.getDefaultLocale();
}
return currentLocale;
}
/** /**
* Get the appropriate localized version of submission-forms.xml according to language settings * Get the appropriate localized version of submission-forms.xml according to language settings

View File

@@ -345,8 +345,8 @@ public class LegacyPluginServiceImpl implements PluginService {
" for interface=" + iname + " for interface=" + iname +
" pluginName=" + name); " pluginName=" + name);
Object result = pluginClass.newInstance(); Object result = pluginClass.newInstance();
if (result instanceof SelfNamedPlugin) { if (result instanceof NameAwarePlugin) {
((SelfNamedPlugin) result).setPluginInstanceName(name); ((NameAwarePlugin) result).setPluginInstanceName(name);
} }
return result; return result;
} }

View File

@@ -0,0 +1,42 @@
/**
* 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.core;
/**
* This is the interface that should be implemented by all the named plugin that
* like to be aware of their name
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
* @version $Revision$
* @see org.dspace.core.service.PluginService
*/
public interface NameAwarePlugin {
/**
* Get the instance's particular name.
* Returns the name by which the class was chosen when
* this instance was created. Only works for instances created
* by <code>PluginService</code>, or if someone remembers to call <code>setPluginName.</code>
* <p>
* Useful when the implementation class wants to be configured differently
* when it is invoked under different names.
*
* @return name or null if not available.
*/
public String getPluginInstanceName();
/**
* Set the name under which this plugin was instantiated.
* Not to be invoked by application code, it is
* called automatically by <code>PluginService.getNamedPlugin()</code>
* when the plugin is instantiated.
*
* @param name -- name used to select this class.
*/
public void setPluginInstanceName(String name);
}

View File

@@ -28,7 +28,7 @@ package org.dspace.core;
* @version $Revision$ * @version $Revision$
* @see org.dspace.core.service.PluginService * @see org.dspace.core.service.PluginService
*/ */
public abstract class SelfNamedPlugin { public abstract class SelfNamedPlugin implements NameAwarePlugin {
// the specific alias used to find the class that created this instance. // the specific alias used to find the class that created this instance.
private String myName = null; private String myName = null;
@@ -52,30 +52,13 @@ public abstract class SelfNamedPlugin {
return null; return null;
} }
/** @Override
* Get an instance's particular name.
* Returns the name by which the class was chosen when
* this instance was created. Only works for instances created
* by <code>PluginService</code>, or if someone remembers to call <code>setPluginName.</code>
* <p>
* Useful when the implementation class wants to be configured differently
* when it is invoked under different names.
*
* @return name or null if not available.
*/
public String getPluginInstanceName() { public String getPluginInstanceName() {
return myName; return myName;
} }
/** @Override
* Set the name under which this plugin was instantiated. public void setPluginInstanceName(String name) {
* Not to be invoked by application code, it is
* called automatically by <code>PluginService.getNamedPlugin()</code>
* when the plugin is instantiated.
*
* @param name -- name used to select this class.
*/
protected void setPluginInstanceName(String name) {
myName = name; myName = name;
} }
} }

View File

@@ -17,6 +17,7 @@ import org.apache.logging.log4j.Logger;
import org.apache.solr.common.SolrInputDocument; import org.apache.solr.common.SolrInputDocument;
import org.dspace.browse.BrowseException; import org.dspace.browse.BrowseException;
import org.dspace.browse.BrowseIndex; import org.dspace.browse.BrowseIndex;
import org.dspace.content.Collection;
import org.dspace.content.Item; import org.dspace.content.Item;
import org.dspace.content.MetadataValue; import org.dspace.content.MetadataValue;
import org.dspace.content.authority.service.ChoiceAuthorityService; import org.dspace.content.authority.service.ChoiceAuthorityService;
@@ -63,7 +64,7 @@ public class SolrServiceMetadataBrowseIndexingPlugin implements SolrServiceIndex
return; return;
} }
Item item = ((IndexableItem) indexableObject).getIndexedObject(); Item item = ((IndexableItem) indexableObject).getIndexedObject();
Collection collection = item.getOwningCollection();
// Get the currently configured browse indexes // Get the currently configured browse indexes
BrowseIndex[] bis; BrowseIndex[] bis;
try { try {
@@ -175,7 +176,7 @@ public class SolrServiceMetadataBrowseIndexingPlugin implements SolrServiceIndex
true); true);
if (!ignorePrefered) { if (!ignorePrefered) {
preferedLabel = choiceAuthorityService preferedLabel = choiceAuthorityService
.getLabel(values.get(x), values.get(x).getLanguage()); .getLabel(values.get(x), collection, values.get(x).getLanguage());
} }
List<String> variants = null; List<String> variants = null;
@@ -195,7 +196,7 @@ public class SolrServiceMetadataBrowseIndexingPlugin implements SolrServiceIndex
if (!ignoreVariants) { if (!ignoreVariants) {
variants = choiceAuthorityService variants = choiceAuthorityService
.getVariants( .getVariants(
values.get(x)); values.get(x), collection);
} }
if (StringUtils if (StringUtils

View File

@@ -173,6 +173,8 @@ public class ItemIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl<Indexable
public void addDiscoveryFields(SolrInputDocument doc, Context context, Item item, public void addDiscoveryFields(SolrInputDocument doc, Context context, Item item,
List<DiscoveryConfiguration> discoveryConfigurations) List<DiscoveryConfiguration> discoveryConfigurations)
throws SQLException, IOException { throws SQLException, IOException {
// use the item service to retrieve the owning collection also for inprogress submission
Collection collection = (Collection) itemService.getParentObject(context, item);
//Keep a list of our sort values which we added, sort values can only be added once //Keep a list of our sort values which we added, sort values can only be added once
List<String> sortFieldsAdded = new ArrayList<>(); List<String> sortFieldsAdded = new ArrayList<>();
Map<String, List<DiscoverySearchFilter>> searchFilters = null; Map<String, List<DiscoverySearchFilter>> searchFilters = null;
@@ -359,7 +361,7 @@ public class ItemIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl<Indexable
if (!ignorePrefered) { if (!ignorePrefered) {
preferedLabel = choiceAuthorityService preferedLabel = choiceAuthorityService
.getLabel(meta, meta.getLanguage()); .getLabel(meta, collection, meta.getLanguage());
} }
boolean ignoreVariants = boolean ignoreVariants =
@@ -375,7 +377,7 @@ public class ItemIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl<Indexable
true); true);
if (!ignoreVariants) { if (!ignoreVariants) {
variants = choiceAuthorityService variants = choiceAuthorityService
.getVariants(meta); .getVariants(meta, collection);
} }
} }

View File

@@ -109,6 +109,11 @@ plugin.sequence.java.util.Collection = \
java.util.Stack, \ java.util.Stack, \
java.util.TreeSet java.util.TreeSet
# Enable a test authority control on dc.language.iso field
choices.plugin.dc.language.iso = common_iso_languages
choices.presentation.dc.language.iso = select
authority.controlled.dc.language.iso = true
########################################### ###########################################
# PROPERTIES USED TO TEST CONFIGURATION # # PROPERTIES USED TO TEST CONFIGURATION #
# PROPERTY EXPOSURE VIA REST # # PROPERTY EXPOSURE VIA REST #

View File

@@ -282,6 +282,70 @@ it, please enter the types and the actual numbers or codes.</hint>
</row> </row>
</form> </form>
<form name="languagetest">
<row>
<field>
<dc-schema>dc</dc-schema>
<dc-element>contributor</dc-element>
<dc-qualifier>author</dc-qualifier>
<label>Author</label>
<input-type>name</input-type>
<repeatable>false</repeatable>
<required>You must enter at least the author.</required>
<hint>Enter the names of the authors of this item in the form Lastname, Firstname [i.e. Smith, Josh or Smith, J].</hint>
</field>
</row>
<row>
<field>
<dc-schema>person</dc-schema>
<dc-element>affiliation</dc-element>
<dc-qualifier>name</dc-qualifier>
<label>Affiliation</label>
<input-type>onebox</input-type>
<repeatable>false</repeatable>
<required />
<hint>Enter the affiliation of the author as stated on the publication.</hint>
</field>
</row>
</form>
<form name="sampleauthority">
<row>
<field>
<dc-schema>dc</dc-schema>
<dc-element>contributor</dc-element>
<dc-qualifier>author</dc-qualifier>
<repeatable>true</repeatable>
<label>Author</label>
<input-type>onebox</input-type>
<hint>Author field that can be associated with an authority providing suggestion</hint>
<required></required>
</field>
</row>
<row>
<field>
<dc-schema>dc</dc-schema>
<dc-element>contributor</dc-element>
<dc-qualifier>editor</dc-qualifier>
<repeatable>false</repeatable>
<label>Editor</label>
<input-type>name</input-type>
<hint>Editor field that can be associated with an authority providing the special name lookup</hint>
<required></required>
</field>
</row>
<row>
<field>
<dc-schema>dc</dc-schema>
<dc-element>subject</dc-element>
<repeatable>true</repeatable>
<label>Subject</label>
<input-type>onebox</input-type>
<hint>Subject field that can be associated with an authority providing lookup</hint>
<required></required>
</field>
</row>
</form>
</form-definitions> </form-definitions>

View File

@@ -490,8 +490,8 @@ public class ItemTest extends AbstractDSpaceObjectTest {
// Set the item to have two pieces of metadata for dc.type and dc2.type // Set the item to have two pieces of metadata for dc.type and dc2.type
String dcType = "DC-TYPE"; String dcType = "DC-TYPE";
String testType = "TEST-TYPE"; String testType = "TEST-TYPE";
itemService.addMetadata(context, it, "dc", "type", null, null, dcType, "accepted", 0); itemService.addMetadata(context, it, "dc", "type", null, null, dcType);
itemService.addMetadata(context, it, "test", "type", null, null, testType, "accepted", 0); itemService.addMetadata(context, it, "test", "type", null, null, testType);
// Check that only one is returned when we ask for all dc.type values // Check that only one is returned when we ask for all dc.type values
List<MetadataValue> values = itemService.getMetadata(it, "dc", "type", null, null); List<MetadataValue> values = itemService.getMetadata(it, "dc", "type", null, null);

View File

@@ -78,7 +78,7 @@ public class DSpaceControlledVocabularyTest extends AbstractDSpaceTest {
String text = "north 40"; String text = "north 40";
Collection collection = null; Collection collection = null;
int start = 0; int start = 0;
int limit = 0; int limit = 10;
String locale = null; String locale = null;
// This "farm" Controlled Vocab is included in TestEnvironment data // This "farm" Controlled Vocab is included in TestEnvironment data
// (under /src/test/data/dspaceFolder/) and it should be auto-loaded // (under /src/test/data/dspaceFolder/) and it should be auto-loaded
@@ -86,8 +86,7 @@ public class DSpaceControlledVocabularyTest extends AbstractDSpaceTest {
DSpaceControlledVocabulary instance = (DSpaceControlledVocabulary) DSpaceControlledVocabulary instance = (DSpaceControlledVocabulary)
CoreServiceFactory.getInstance().getPluginService().getNamedPlugin(Class.forName(PLUGIN_INTERFACE), "farm"); CoreServiceFactory.getInstance().getPluginService().getNamedPlugin(Class.forName(PLUGIN_INTERFACE), "farm");
assertNotNull(instance); assertNotNull(instance);
Choices result = instance.getMatches(field, text, collection, start, Choices result = instance.getMatches(text, start, limit, locale);
limit, locale);
assertEquals("the farm::north 40", result.values[0].value); assertEquals("the farm::north 40", result.values[0].value);
} }

View File

@@ -149,7 +149,7 @@ public class RestResourceController implements InitializingBean {
* @return single DSpaceResource * @return single DSpaceResource
*/ */
@RequestMapping(method = RequestMethod.GET, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_DIGIT) @RequestMapping(method = RequestMethod.GET, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_DIGIT)
public DSpaceResource<RestAddressableModel> findOne(@PathVariable String apiCategory, @PathVariable String model, public HALResource<RestAddressableModel> findOne(@PathVariable String apiCategory, @PathVariable String model,
@PathVariable Integer id) { @PathVariable Integer id) {
return findOneInternal(apiCategory, model, id); return findOneInternal(apiCategory, model, id);
} }
@@ -180,7 +180,7 @@ public class RestResourceController implements InitializingBean {
* @return single DSpaceResource * @return single DSpaceResource
*/ */
@RequestMapping(method = RequestMethod.GET, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG) @RequestMapping(method = RequestMethod.GET, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG)
public DSpaceResource<RestAddressableModel> findOne(@PathVariable String apiCategory, @PathVariable String model, public HALResource<RestAddressableModel> findOne(@PathVariable String apiCategory, @PathVariable String model,
@PathVariable String id) { @PathVariable String id) {
return findOneInternal(apiCategory, model, id); return findOneInternal(apiCategory, model, id);
} }
@@ -200,7 +200,7 @@ public class RestResourceController implements InitializingBean {
* @return single DSpaceResource * @return single DSpaceResource
*/ */
@RequestMapping(method = RequestMethod.GET, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID) @RequestMapping(method = RequestMethod.GET, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID)
public DSpaceResource<RestAddressableModel> findOne(@PathVariable String apiCategory, @PathVariable String model, public HALResource<RestAddressableModel> findOne(@PathVariable String apiCategory, @PathVariable String model,
@PathVariable UUID uuid) { @PathVariable UUID uuid) {
return findOneInternal(apiCategory, model, uuid); return findOneInternal(apiCategory, model, uuid);
} }
@@ -213,7 +213,7 @@ public class RestResourceController implements InitializingBean {
* @param id Identifier from request * @param id Identifier from request
* @return single DSpaceResource * @return single DSpaceResource
*/ */
private <ID extends Serializable> DSpaceResource<RestAddressableModel> findOneInternal(String apiCategory, private <ID extends Serializable> HALResource<RestAddressableModel> findOneInternal(String apiCategory,
String model, ID id) { String model, ID id) {
DSpaceRestRepository<RestAddressableModel, ID> repository = utils.getResourceRepository(apiCategory, model); DSpaceRestRepository<RestAddressableModel, ID> repository = utils.getResourceRepository(apiCategory, model);
Optional<RestAddressableModel> modelObject = Optional.empty(); Optional<RestAddressableModel> modelObject = Optional.empty();
@@ -799,7 +799,7 @@ public class RestResourceController implements InitializingBean {
Method linkMethod = utils.requireMethod(linkRepository.getClass(), linkRest.method()); Method linkMethod = utils.requireMethod(linkRepository.getClass(), linkRest.method());
try { try {
if (Page.class.isAssignableFrom(linkMethod.getReturnType())) { if (Page.class.isAssignableFrom(linkMethod.getReturnType())) {
Page<? extends RestModel> pageResult = (Page<? extends RestAddressableModel>) linkMethod Page<? extends RestModel> pageResult = (Page<? extends RestModel>) linkMethod
.invoke(linkRepository, request, uuid, page, utils.obtainProjection()); .invoke(linkRepository, request, uuid, page, utils.obtainProjection());
if (pageResult == null) { if (pageResult == null) {
@@ -823,8 +823,8 @@ public class RestResourceController implements InitializingBean {
return new EntityModel(new EmbeddedPage(link.getHref(), return new EntityModel(new EmbeddedPage(link.getHref(),
pageResult.map(converter::toResource), null, subpath)); pageResult.map(converter::toResource), null, subpath));
} else { } else {
RestModel object = (RestModel) linkMethod.invoke(linkRepository, request, uuid, page, RestModel object = (RestModel) linkMethod.invoke(linkRepository, request,
utils.obtainProjection()); uuid, page, utils.obtainProjection());
if (object == null) { if (object == null) {
response.setStatus(HttpServletResponse.SC_NO_CONTENT); response.setStatus(HttpServletResponse.SC_NO_CONTENT);
return null; return null;
@@ -846,7 +846,7 @@ public class RestResourceController implements InitializingBean {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
RestAddressableModel modelObject = repository.findById(uuid).orElse(null); RestModel modelObject = repository.findById(uuid).orElse(null);
if (modelObject == null) { if (modelObject == null) {
throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + uuid + " not found"); throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + uuid + " not found");

View File

@@ -115,14 +115,16 @@ public class SubmissionFormConverter implements DSpaceConverter<DCInputSet, Subm
String inputType = dcinput.getInputType(); String inputType = dcinput.getInputType();
SelectableMetadata selMd = new SelectableMetadata(); SelectableMetadata selMd = new SelectableMetadata();
if (authorityUtils.isChoice(dcinput.getSchema(), dcinput.getElement(), dcinput.getQualifier())) { if (isChoice(dcinput.getSchema(), dcinput.getElement(), dcinput.getQualifier(),
dcinput.getPairsType(), dcinput.getVocabulary())) {
inputRest.setType(getPresentation(dcinput.getSchema(), dcinput.getElement(), inputRest.setType(getPresentation(dcinput.getSchema(), dcinput.getElement(),
dcinput.getQualifier(), inputType)); dcinput.getQualifier(), inputType));
selMd.setAuthority(getAuthorityName(dcinput.getSchema(), dcinput.getElement(), selMd.setControlledVocabulary(getAuthorityName(dcinput.getSchema(), dcinput.getElement(),
dcinput.getQualifier(), dcinput.getPairsType(), dcinput.getQualifier(), dcinput.getPairsType(),
dcinput.getVocabulary())); dcinput.getVocabulary()));
selMd.setClosed( selMd.setClosed(
authorityUtils.isClosed(dcinput.getSchema(), dcinput.getElement(), dcinput.getQualifier())); isClosed(dcinput.getSchema(), dcinput.getElement(), dcinput.getQualifier(),
dcinput.getPairsType(), dcinput.getVocabulary()));
} else { } else {
inputRest.setType(inputType); inputRest.setType(inputType);
} }
@@ -139,10 +141,10 @@ public class SubmissionFormConverter implements DSpaceConverter<DCInputSet, Subm
selMd.setMetadata(org.dspace.core.Utils selMd.setMetadata(org.dspace.core.Utils
.standardize(dcinput.getSchema(), dcinput.getElement(), pairs.get(idx + 1), ".")); .standardize(dcinput.getSchema(), dcinput.getElement(), pairs.get(idx + 1), "."));
if (authorityUtils.isChoice(dcinput.getSchema(), dcinput.getElement(), dcinput.getQualifier())) { if (authorityUtils.isChoice(dcinput.getSchema(), dcinput.getElement(), dcinput.getQualifier())) {
selMd.setAuthority(getAuthorityName(dcinput.getSchema(), dcinput.getElement(), selMd.setControlledVocabulary(getAuthorityName(dcinput.getSchema(), dcinput.getElement(),
pairs.get(idx + 1), dcinput.getPairsType(), dcinput.getVocabulary())); pairs.get(idx + 1), dcinput.getPairsType(), dcinput.getVocabulary()));
selMd.setClosed(authorityUtils.isClosed(dcinput.getSchema(), dcinput.getElement(), selMd.setClosed(isClosed(dcinput.getSchema(), dcinput.getElement(),
dcinput.getQualifier())); dcinput.getQualifier(), null, dcinput.getVocabulary()));
} }
selectableMetadata.add(selMd); selectableMetadata.add(selMd);
} }
@@ -185,7 +187,8 @@ public class SubmissionFormConverter implements DSpaceConverter<DCInputSet, Subm
return INPUT_TYPE_LOOKUP; return INPUT_TYPE_LOOKUP;
} }
} else if (INPUT_TYPE_NAME.equals(inputType)) { } else if (INPUT_TYPE_NAME.equals(inputType)) {
if (AuthorityUtils.PRESENTATION_TYPE_LOOKUP.equals(presentation)) { if (AuthorityUtils.PRESENTATION_TYPE_LOOKUP.equals(presentation) ||
AuthorityUtils.PRESENTATION_TYPE_AUTHORLOOKUP.equals(presentation)) {
return INPUT_TYPE_LOOKUP_NAME; return INPUT_TYPE_LOOKUP_NAME;
} }
} }
@@ -203,6 +206,22 @@ public class SubmissionFormConverter implements DSpaceConverter<DCInputSet, Subm
return authorityUtils.getAuthorityName(schema, element, qualifier); return authorityUtils.getAuthorityName(schema, element, qualifier);
} }
private boolean isClosed(String schema, String element, String qualifier, String valuePairsName,
String vocabularyName) {
if (StringUtils.isNotBlank(valuePairsName) || StringUtils.isNotBlank(vocabularyName)) {
return true;
}
return authorityUtils.isClosed(schema, element, qualifier);
}
private boolean isChoice(String schema, String element, String qualifier, String valuePairsName,
String vocabularyName) {
if (StringUtils.isNotBlank(valuePairsName) || StringUtils.isNotBlank(vocabularyName)) {
return true;
}
return authorityUtils.isChoice(schema, element, qualifier);
}
@Override @Override
public Class<DCInputSet> getModelClass() { public Class<DCInputSet> getModelClass() {
return DCInputSet.class; return DCInputSet.class;

View File

@@ -7,7 +7,7 @@
*/ */
package org.dspace.app.rest.converter; package org.dspace.app.rest.converter;
import org.dspace.app.rest.model.AuthorityEntryRest; import org.dspace.app.rest.model.VocabularyEntryDetailsRest;
import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.projection.Projection;
import org.dspace.app.rest.utils.AuthorityUtils; import org.dspace.app.rest.utils.AuthorityUtils;
import org.dspace.content.authority.Choice; import org.dspace.content.authority.Choice;
@@ -22,16 +22,17 @@ import org.springframework.stereotype.Component;
* @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it)
*/ */
@Component @Component
public class AuthorityEntryRestConverter implements DSpaceConverter<Choice, AuthorityEntryRest> { public class VocabularyEntryDetailsRestConverter implements DSpaceConverter<Choice, VocabularyEntryDetailsRest> {
@Override @Override
public AuthorityEntryRest convert(Choice choice, Projection projection) { public VocabularyEntryDetailsRest convert(Choice choice, Projection projection) {
AuthorityEntryRest entry = new AuthorityEntryRest(); VocabularyEntryDetailsRest entry = new VocabularyEntryDetailsRest();
entry.setProjection(projection); entry.setProjection(projection);
entry.setValue(choice.value); entry.setValue(choice.value);
entry.setDisplay(choice.label); entry.setDisplay(choice.label);
entry.setId(choice.authority); entry.setId(choice.authority);
entry.setOtherInformation(choice.extras); entry.setOtherInformation(choice.extras);
entry.setSelectable(choice.selectable);
return entry; return entry;
} }

View File

@@ -7,7 +7,7 @@
*/ */
package org.dspace.app.rest.converter; package org.dspace.app.rest.converter;
import org.dspace.app.rest.model.AuthorityRest; import org.dspace.app.rest.model.VocabularyRest;
import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.projection.Projection;
import org.dspace.app.rest.utils.AuthorityUtils; import org.dspace.app.rest.utils.AuthorityUtils;
import org.dspace.content.authority.ChoiceAuthority; import org.dspace.content.authority.ChoiceAuthority;
@@ -23,15 +23,15 @@ import org.springframework.stereotype.Component;
* @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it)
*/ */
@Component @Component
public class AuthorityRestConverter implements DSpaceConverter<ChoiceAuthority, AuthorityRest> { public class VocabularyRestConverter implements DSpaceConverter<ChoiceAuthority, VocabularyRest> {
@Override @Override
public AuthorityRest convert(ChoiceAuthority step, Projection projection) { public VocabularyRest convert(ChoiceAuthority authority, Projection projection) {
AuthorityRest authorityRest = new AuthorityRest(); VocabularyRest authorityRest = new VocabularyRest();
authorityRest.setProjection(projection); authorityRest.setProjection(projection);
authorityRest.setHierarchical(step.isHierarchical()); authorityRest.setHierarchical(authority.isHierarchical());
authorityRest.setScrollable(step.isScrollable()); authorityRest.setScrollable(authority.isScrollable());
authorityRest.setIdentifier(step.hasIdentifier()); authorityRest.setPreloadLevel(authority.getPreloadLevel());
return authorityRest; return authorityRest;
} }

View File

@@ -0,0 +1,30 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* This is the exception to capture details about a not existing linked resource
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
*/
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "This link is not found in the system")
public class LinkNotFoundException extends RuntimeException {
String apiCategory;
String model;
String id;
public LinkNotFoundException(String apiCategory, String model, String id) {
this.apiCategory = apiCategory;
this.model = model;
this.id = id;
}
}

View File

@@ -1,66 +0,0 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.link;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import java.util.LinkedList;
import org.atteo.evo.inflector.English;
import org.dspace.app.rest.RestResourceController;
import org.dspace.app.rest.model.AuthorityEntryRest;
import org.dspace.app.rest.model.AuthorityRest;
import org.dspace.app.rest.model.hateoas.AuthorityEntryResource;
import org.dspace.app.rest.utils.AuthorityUtils;
import org.springframework.data.domain.Pageable;
import org.springframework.hateoas.IanaLinkRelations;
import org.springframework.hateoas.Link;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;
/**
* This class' purpose is to provide a factory to add links to the AuthorityEntryResource. The addLinks factory will
* be called
* from the HalLinkService class addLinks method.
*/
@Component
public class AuthorityEntryHalLinkFactory extends HalLinkFactory<AuthorityEntryResource, RestResourceController> {
protected void addLinks(final AuthorityEntryResource halResource, final Pageable pageable,
final LinkedList<Link> list) throws Exception {
AuthorityEntryRest entry = halResource.getContent();
if (entry.getOtherInformation() != null) {
if (entry.getOtherInformation().containsKey(AuthorityUtils.RESERVED_KEYMAP_PARENT)) {
UriComponentsBuilder uriComponentsBuilder = linkTo(
getMethodOn(AuthorityRest.CATEGORY, AuthorityRest.NAME)
.findRel(null, null, AuthorityRest.CATEGORY,
English.plural(AuthorityRest.NAME),
entry.getAuthorityName() + "/" + AuthorityRest.ENTRY,
entry.getOtherInformation().get(AuthorityUtils.RESERVED_KEYMAP_PARENT), null, null))
.toUriComponentsBuilder();
list.add(buildLink(AuthorityUtils.RESERVED_KEYMAP_PARENT, uriComponentsBuilder.build().toString()));
}
}
String selfLinkString = linkTo(
getMethodOn().findOne(entry.getCategory(), English.plural(entry.getType()), entry.getAuthorityName()))
.toUriComponentsBuilder().build().toString() + "/entryValues/" + entry.getId();
list.add(buildLink(IanaLinkRelations.SELF.value(), selfLinkString));
}
protected Class<RestResourceController> getControllerClass() {
return RestResourceController.class;
}
protected Class<AuthorityEntryResource> getResourceClass() {
return AuthorityEntryResource.class;
}
}

View File

@@ -21,7 +21,6 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include;
public class SubmissionFormInputTypeRest { public class SubmissionFormInputTypeRest {
private String type; private String type;
private String regex; private String regex;
private AuthorityRest authority;
public String getType() { public String getType() {
return type; return type;
@@ -39,11 +38,4 @@ public class SubmissionFormInputTypeRest {
this.regex = regex; this.regex = regex;
} }
public AuthorityRest getAuthority() {
return authority;
}
public void setAuthority(AuthorityRest authority) {
this.authority = authority;
}
} }

View File

@@ -0,0 +1,104 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.dspace.app.rest.RestResourceController;
/**
* The Vocabulary Entry Details REST Resource
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
*/
@LinksRest(links = {
@LinkRest(name = VocabularyEntryDetailsRest.PARENT, method = "getParent"),
@LinkRest(name = VocabularyEntryDetailsRest.CHILDREN, method = "getChildren")
})
public class VocabularyEntryDetailsRest extends BaseObjectRest<String> {
public static final String NAME = "vocabularyEntryDetail";
public static final String PARENT = "parent";
public static final String CHILDREN = "children";
private String display;
private String value;
private Map<String, String> otherInformation;
private boolean selectable;
@JsonIgnore
private boolean inHierarchicalVocabulary = false;
@JsonIgnore
private String vocabularyName;
public String getDisplay() {
return display;
}
public void setDisplay(String value) {
this.display = value;
}
public Map<String, String> getOtherInformation() {
return otherInformation;
}
public void setOtherInformation(Map<String, String> otherInformation) {
this.otherInformation = otherInformation;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public static String getName() {
return NAME;
}
public String getVocabularyName() {
return vocabularyName;
}
public void setVocabularyName(String vocabularyName) {
this.vocabularyName = vocabularyName;
}
@Override
public String getCategory() {
return VocabularyRest.CATEGORY;
}
@Override
public String getType() {
return VocabularyEntryDetailsRest.NAME;
}
@Override
public Class getController() {
return RestResourceController.class;
}
public Boolean isSelectable() {
return selectable;
}
public void setSelectable(Boolean selectable) {
this.selectable = selectable;
}
public void setInHierarchicalVocabulary(boolean isInHierarchicalVocabulary) {
this.inHierarchicalVocabulary = isInHierarchicalVocabulary;
}
public boolean isInHierarchicalVocabulary() {
return inHierarchicalVocabulary;
}
}

View File

@@ -10,30 +10,28 @@ package org.dspace.app.rest.model;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import org.dspace.app.rest.RestResourceController; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
/** /**
* The Authority Entry REST Resource * An entry in a Vocabulary
* *
* @author Andrea Bollini (andrea.bollini at 4science.it) * @author Andrea Bollini (andrea.bollini at 4science.it)
*/ */
public class AuthorityEntryRest extends RestAddressableModel { public class VocabularyEntryRest implements RestModel {
public static final String NAME = "authorityEntry"; public static final String NAME = "vocabularyEntry";
private String id;
@JsonInclude(Include.NON_NULL)
private String authority;
private String display; private String display;
private String value; private String value;
private Map<String, String> otherInformation; private Map<String, String> otherInformation;
/**
* The Vocabulary Entry Details resource if available related to this entry
*/
@JsonIgnore @JsonIgnore
private String authorityName; private VocabularyEntryDetailsRest vocabularyEntryDetailsRest;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getDisplay() { public String getDisplay() {
return display; return display;
@@ -59,31 +57,24 @@ public class AuthorityEntryRest extends RestAddressableModel {
this.value = value; this.value = value;
} }
public static String getName() { public void setAuthority(String authority) {
return NAME; this.authority = authority;
} }
public String getAuthorityName() { public String getAuthority() {
return authorityName; return authority;
} }
public void setAuthorityName(String authorityName) { public void setVocabularyEntryDetailsRest(VocabularyEntryDetailsRest vocabularyEntryDetailsRest) {
this.authorityName = authorityName; this.vocabularyEntryDetailsRest = vocabularyEntryDetailsRest;
} }
@Override public VocabularyEntryDetailsRest getVocabularyEntryDetailsRest() {
public String getCategory() { return vocabularyEntryDetailsRest;
return AuthorityRest.CATEGORY;
} }
@Override @Override
public String getType() { public String getType() {
return AuthorityRest.NAME; return VocabularyEntryRest.NAME;
} }
@Override
public Class getController() {
return RestResourceController.class;
}
} }

View File

@@ -10,25 +10,20 @@ package org.dspace.app.rest.model;
import org.dspace.app.rest.RestResourceController; import org.dspace.app.rest.RestResourceController;
/** /**
* The authority REST resource * The vocabulary REST resource
* *
* @author Andrea Bollini (andrea.bollini at 4science.it) * @author Andrea Bollini (andrea.bollini at 4science.it)
*/ */
@LinksRest(links = { @LinksRest(links = {
@LinkRest(name = AuthorityRest.ENTRIES, @LinkRest(name = VocabularyRest.ENTRIES,
method = "query" method = "filter"
), ),
@LinkRest(
name = AuthorityRest.ENTRY,
method = "getResource"
)
}) })
public class AuthorityRest extends BaseObjectRest<String> { public class VocabularyRest extends BaseObjectRest<String> {
public static final String NAME = "authority"; public static final String NAME = "vocabulary";
public static final String CATEGORY = RestAddressableModel.INTEGRATION; public static final String CATEGORY = RestAddressableModel.SUBMISSION;
public static final String ENTRIES = "entries"; public static final String ENTRIES = "entries";
public static final String ENTRY = "entryValues";
private String name; private String name;
@@ -36,7 +31,7 @@ public class AuthorityRest extends BaseObjectRest<String> {
private boolean hierarchical; private boolean hierarchical;
private boolean identifier; private Integer preloadLevel;
@Override @Override
public String getId() { public String getId() {
@@ -67,6 +62,14 @@ public class AuthorityRest extends BaseObjectRest<String> {
this.hierarchical = hierarchical; this.hierarchical = hierarchical;
} }
public Integer getPreloadLevel() {
return preloadLevel;
}
public void setPreloadLevel(Integer preloadLevel) {
this.preloadLevel = preloadLevel;
}
@Override @Override
public String getType() { public String getType() {
return NAME; return NAME;
@@ -81,12 +84,4 @@ public class AuthorityRest extends BaseObjectRest<String> {
public String getCategory() { public String getCategory() {
return CATEGORY; return CATEGORY;
} }
public boolean hasIdentifier() {
return identifier;
}
public void setIdentifier(boolean identifier) {
this.identifier = identifier;
}
} }

View File

@@ -1,26 +0,0 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model.hateoas;
import org.dspace.app.rest.model.AuthorityEntryRest;
import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource;
/**
* Authority Rest HAL Resource. The HAL Resource wraps the REST Resource adding
* support for the links and embedded resources
*
* @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it)
*/
@RelNameDSpaceResource(AuthorityEntryRest.NAME)
public class AuthorityEntryResource extends HALResource<AuthorityEntryRest> {
public AuthorityEntryResource(AuthorityEntryRest entry) {
super(entry);
}
}

View File

@@ -0,0 +1,30 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model.hateoas;
import org.dspace.app.rest.model.VocabularyEntryDetailsRest;
import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource;
import org.dspace.app.rest.utils.Utils;
/**
* Vocabulary Entry Details Rest HAL Resource. The HAL Resource wraps the REST Resource adding
* support for the links and embedded resources
*
* @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it)
*/
@RelNameDSpaceResource(VocabularyEntryDetailsRest.NAME)
public class VocabularyEntryDetailsResource extends DSpaceResource<VocabularyEntryDetailsRest> {
public VocabularyEntryDetailsResource(VocabularyEntryDetailsRest entry, Utils utils) {
super(entry, utils);
if (entry.isInHierarchicalVocabulary()) {
add(utils.linkToSubResource(entry, VocabularyEntryDetailsRest.PARENT));
add(utils.linkToSubResource(entry, VocabularyEntryDetailsRest.CHILDREN));
}
}
}

View File

@@ -0,0 +1,24 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model.hateoas;
import org.dspace.app.rest.model.VocabularyEntryRest;
import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource;
/**
* Vocabulary Entry Rest HAL Resource. The HAL Resource wraps the REST Resource
* adding support for the links and embedded resources
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it)
*/
@RelNameDSpaceResource(VocabularyEntryRest.NAME)
public class VocabularyEntryResource extends HALResource<VocabularyEntryRest> {
public VocabularyEntryResource(VocabularyEntryRest sd) {
super(sd);
}
}

View File

@@ -7,7 +7,7 @@
*/ */
package org.dspace.app.rest.model.hateoas; package org.dspace.app.rest.model.hateoas;
import org.dspace.app.rest.model.AuthorityRest; import org.dspace.app.rest.model.VocabularyRest;
import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource;
import org.dspace.app.rest.utils.Utils; import org.dspace.app.rest.utils.Utils;
@@ -17,13 +17,10 @@ import org.dspace.app.rest.utils.Utils;
* *
* @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it)
*/ */
@RelNameDSpaceResource(AuthorityRest.NAME) @RelNameDSpaceResource(VocabularyRest.NAME)
public class AuthorityResource extends DSpaceResource<AuthorityRest> { public class VocabularyResource extends DSpaceResource<VocabularyRest> {
public AuthorityResource(AuthorityRest sd, Utils utils) { public VocabularyResource(VocabularyRest sd, Utils utils) {
super(sd, utils); super(sd, utils);
if (sd.hasIdentifier()) { add(utils.linkToSubResource(sd, VocabularyRest.ENTRIES));
add(utils.linkToSubResource(sd, AuthorityRest.ENTRY));
}
add(utils.linkToSubResource(sd, AuthorityRest.ENTRIES));
} }
} }

View File

@@ -7,6 +7,9 @@
*/ */
package org.dspace.app.rest.model.submit; package org.dspace.app.rest.model.submit;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
/** /**
* The SelectableMetadata REST Resource. It is not addressable directly, only * The SelectableMetadata REST Resource. It is not addressable directly, only
* used as inline object in the InputForm resource. * used as inline object in the InputForm resource.
@@ -24,7 +27,9 @@ package org.dspace.app.rest.model.submit;
public class SelectableMetadata { public class SelectableMetadata {
private String metadata; private String metadata;
private String label; private String label;
private String authority; @JsonInclude(Include.NON_NULL)
private String controlledVocabulary;
@JsonInclude(Include.NON_NULL)
private Boolean closed = false; private Boolean closed = false;
public String getMetadata() { public String getMetadata() {
@@ -43,12 +48,12 @@ public class SelectableMetadata {
this.label = label; this.label = label;
} }
public void setAuthority(String authority) { public void setControlledVocabulary(String vocabularyName) {
this.authority = authority; this.controlledVocabulary = vocabularyName;
} }
public String getAuthority() { public String getControlledVocabulary() {
return authority; return controlledVocabulary;
} }
public Boolean isClosed() { public Boolean isClosed() {

View File

@@ -1,82 +0,0 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.repository;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.dspace.app.rest.model.AuthorityEntryRest;
import org.dspace.app.rest.model.AuthorityRest;
import org.dspace.app.rest.projection.Projection;
import org.dspace.app.rest.utils.AuthorityUtils;
import org.dspace.content.Collection;
import org.dspace.content.authority.Choice;
import org.dspace.content.authority.Choices;
import org.dspace.content.authority.service.ChoiceAuthorityService;
import org.dspace.content.service.CollectionService;
import org.dspace.core.Context;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
/**
* Controller for exposition of authority services
*
* @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it)
*/
@Component(AuthorityRest.CATEGORY + "." + AuthorityRest.NAME + "." + AuthorityRest.ENTRIES)
public class AuthorityEntryLinkRepository extends AbstractDSpaceRestRepository
implements LinkRestRepository {
@Autowired
private ChoiceAuthorityService cas;
@Autowired
private CollectionService cs;
@Autowired
private AuthorityUtils authorityUtils;
@PreAuthorize("hasAuthority('AUTHENTICATED')")
public Page<AuthorityEntryRest> query(@Nullable HttpServletRequest request, String name,
@Nullable Pageable optionalPageable, Projection projection) {
Context context = obtainContext();
String query = request == null ? null : request.getParameter("query");
String metadata = request == null ? null : request.getParameter("metadata");
String uuidCollectìon = request == null ? null : request.getParameter("uuid");
Collection collection = null;
if (StringUtils.isNotBlank(uuidCollectìon)) {
try {
collection = cs.find(context, UUID.fromString(uuidCollectìon));
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
List<AuthorityEntryRest> results = new ArrayList<>();
Pageable pageable = utils.getPageable(optionalPageable);
if (StringUtils.isNotBlank(metadata)) {
String[] tokens = org.dspace.core.Utils.tokenize(metadata);
String fieldKey = org.dspace.core.Utils.standardize(tokens[0], tokens[1], tokens[2], "_");
Choices choices = cas.getMatches(fieldKey, query, collection, Math.toIntExact(pageable.getOffset()),
pageable.getPageSize(),
context.getCurrentLocale().toString());
for (Choice value : choices.values) {
results.add(authorityUtils.convertEntry(value, name, projection));
}
}
return new PageImpl<>(results, pageable, results.size());
}
}

View File

@@ -1,60 +0,0 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.repository;
import javax.servlet.http.HttpServletRequest;
import org.dspace.app.rest.model.AuthorityEntryRest;
import org.dspace.app.rest.model.AuthorityRest;
import org.dspace.app.rest.projection.Projection;
import org.dspace.app.rest.utils.AuthorityUtils;
import org.dspace.content.authority.Choice;
import org.dspace.content.authority.ChoiceAuthority;
import org.dspace.content.authority.service.ChoiceAuthorityService;
import org.dspace.core.Context;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
/**
* Controller for exposition of authority services
*
* @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it)
*/
@Component(AuthorityRest.CATEGORY + "." + AuthorityRest.NAME + "." + AuthorityRest.ENTRY)
public class AuthorityEntryValueLinkRepository extends AbstractDSpaceRestRepository
implements LinkRestRepository {
@Autowired
private ChoiceAuthorityService cas;
@Autowired
private AuthorityUtils authorityUtils;
@PreAuthorize("hasAuthority('AUTHENTICATED')")
public AuthorityEntryRest getResource(HttpServletRequest request, String name, String relId,
Pageable pageable, Projection projection) {
Context context = obtainContext();
ChoiceAuthority choiceAuthority = cas.getChoiceAuthorityByAuthorityName(name);
Choice choice = choiceAuthority.getChoice(null, relId, context.getCurrentLocale().toString());
if (choice == null) {
throw new ResourceNotFoundException("The authority was not found");
}
return authorityUtils.convertEntry(choice, name, projection);
}
/**
* Not embeddable because this is not currently a pageable subresource.
*/
@Override
public boolean isEmbeddableRelation(Object data, String name) {
return false;
}
}

View File

@@ -1,82 +0,0 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.repository;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import org.dspace.app.rest.DiscoverableEndpointsService;
import org.dspace.app.rest.model.AuthorityRest;
import org.dspace.app.rest.model.AuthorizationRest;
import org.dspace.app.rest.projection.Projection;
import org.dspace.app.rest.utils.AuthorityUtils;
import org.dspace.content.authority.ChoiceAuthority;
import org.dspace.content.authority.service.ChoiceAuthorityService;
import org.dspace.core.Context;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.hateoas.Link;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
/**
* Controller for exposition of authority services
*
* @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it)
*/
@Component(AuthorityRest.CATEGORY + "." + AuthorityRest.NAME)
public class AuthorityRestRepository extends DSpaceRestRepository<AuthorityRest, String>
implements InitializingBean {
@Autowired
private ChoiceAuthorityService cas;
@Autowired
private AuthorityUtils authorityUtils;
@Autowired
DiscoverableEndpointsService discoverableEndpointsService;
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@Override
public AuthorityRest findOne(Context context, String name) {
ChoiceAuthority source = cas.getChoiceAuthorityByAuthorityName(name);
return authorityUtils.convertAuthority(source, name, utils.obtainProjection());
}
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@Override
public Page<AuthorityRest> findAll(Context context, Pageable pageable) {
Set<String> authoritiesName = cas.getChoiceAuthoritiesNames();
List<AuthorityRest> results = new ArrayList<>();
Projection projection = utils.obtainProjection();
for (String authorityName : authoritiesName) {
ChoiceAuthority source = cas.getChoiceAuthorityByAuthorityName(authorityName);
AuthorityRest result = authorityUtils.convertAuthority(source, authorityName, projection);
results.add(result);
}
return new PageImpl<>(results, pageable, results.size());
}
@Override
public Class<AuthorityRest> getDomainClass() {
return AuthorityRest.class;
}
@Override
public void afterPropertiesSet() throws Exception {
discoverableEndpointsService.register(this, Arrays.asList(
new Link("/api/" + AuthorizationRest.CATEGORY + "/" + AuthorizationRest.NAME + "/search",
AuthorizationRest.NAME + "-search")));
}
}

View File

@@ -0,0 +1,77 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.repository;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.dspace.app.rest.exception.LinkNotFoundException;
import org.dspace.app.rest.model.VocabularyEntryDetailsRest;
import org.dspace.app.rest.model.VocabularyRest;
import org.dspace.app.rest.projection.Projection;
import org.dspace.app.rest.utils.AuthorityUtils;
import org.dspace.content.authority.Choice;
import org.dspace.content.authority.ChoiceAuthority;
import org.dspace.content.authority.Choices;
import org.dspace.content.authority.service.ChoiceAuthorityService;
import org.dspace.core.Context;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
/**
* Link repository to expose the parent of a vocabulary entry details in an hierarchical vocabulary
*
* @author Mykhaylo Boychuk (4Science.it)
*/
@Component(VocabularyRest.CATEGORY + "." + VocabularyEntryDetailsRest.NAME + "." + VocabularyEntryDetailsRest.CHILDREN)
public class VocabularyEntryDetailsChildrenLinkRepository extends AbstractDSpaceRestRepository
implements LinkRestRepository {
@Autowired
private ChoiceAuthorityService choiceAuthorityService;
@Autowired
private AuthorityUtils authorityUtils;
@PreAuthorize("hasAuthority('AUTHENTICATED')")
public Page<VocabularyEntryDetailsRest> getChildren(@Nullable HttpServletRequest request, String name,
@Nullable Pageable optionalPageable, Projection projection) {
Context context = obtainContext();
String[] parts = StringUtils.split(name, ":", 2);
if (parts.length != 2) {
return null;
}
String vocabularyName = parts[0];
String id = parts[1];
Pageable pageable = utils.getPageable(optionalPageable);
List<VocabularyEntryDetailsRest> results = new ArrayList<VocabularyEntryDetailsRest>();
ChoiceAuthority authority = choiceAuthorityService.getChoiceAuthorityByAuthorityName(vocabularyName);
if (StringUtils.isNotBlank(id) && authority.isHierarchical()) {
Choices choices = choiceAuthorityService.getChoicesByParent(vocabularyName, id, (int) pageable.getOffset(),
pageable.getPageSize(), context.getCurrentLocale().toString());
for (Choice value : choices.values) {
results.add(authorityUtils.convertEntryDetails(value, vocabularyName, authority.isHierarchical(),
utils.obtainProjection()));
}
Page<VocabularyEntryDetailsRest> resources = new PageImpl<VocabularyEntryDetailsRest>(results, pageable,
choices.total);
return resources;
} else {
throw new LinkNotFoundException(VocabularyRest.CATEGORY, VocabularyEntryDetailsRest.NAME, name);
}
}
}

View File

@@ -0,0 +1,64 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.repository;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.NotFoundException;
import org.apache.commons.lang3.StringUtils;
import org.dspace.app.rest.model.VocabularyEntryDetailsRest;
import org.dspace.app.rest.model.VocabularyRest;
import org.dspace.app.rest.projection.Projection;
import org.dspace.app.rest.utils.AuthorityUtils;
import org.dspace.content.authority.Choice;
import org.dspace.content.authority.ChoiceAuthority;
import org.dspace.content.authority.service.ChoiceAuthorityService;
import org.dspace.core.Context;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
/**
* Link repository to expose the parent of a vocabulary entry details in an hierarchical vocabulary
*
* @author Mykhaylo Boychuk ($science.it)
*/
@Component(VocabularyRest.CATEGORY + "." + VocabularyEntryDetailsRest.NAME + "." + VocabularyEntryDetailsRest.PARENT)
public class VocabularyEntryDetailsParentLinkRepository extends AbstractDSpaceRestRepository
implements LinkRestRepository {
@Autowired
private ChoiceAuthorityService choiceAuthorityService;
@Autowired
private AuthorityUtils authorityUtils;
@PreAuthorize("hasAuthority('AUTHENTICATED')")
public VocabularyEntryDetailsRest getParent(@Nullable HttpServletRequest request, String name,
@Nullable Pageable optionalPageable, Projection projection) {
Context context = obtainContext();
String[] parts = StringUtils.split(name, ":", 2);
if (parts.length != 2) {
return null;
}
String vocabularyName = parts[0];
String id = parts[1];
ChoiceAuthority authority = choiceAuthorityService.getChoiceAuthorityByAuthorityName(vocabularyName);
Choice choice = null;
if (StringUtils.isNotBlank(id) && authority != null && authority.isHierarchical()) {
choice = choiceAuthorityService.getParentChoice(vocabularyName, id, context.getCurrentLocale().toString());
} else {
throw new NotFoundException();
}
return authorityUtils.convertEntryDetails(choice, vocabularyName, authority.isHierarchical(),
utils.obtainProjection());
}
}

View File

@@ -0,0 +1,111 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.repository;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.atteo.evo.inflector.English;
import org.dspace.app.rest.DiscoverableEndpointsService;
import org.dspace.app.rest.Parameter;
import org.dspace.app.rest.SearchRestMethod;
import org.dspace.app.rest.exception.LinkNotFoundException;
import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException;
import org.dspace.app.rest.model.ResourcePolicyRest;
import org.dspace.app.rest.model.VocabularyEntryDetailsRest;
import org.dspace.app.rest.model.VocabularyRest;
import org.dspace.app.rest.utils.AuthorityUtils;
import org.dspace.content.authority.Choice;
import org.dspace.content.authority.ChoiceAuthority;
import org.dspace.content.authority.Choices;
import org.dspace.content.authority.service.ChoiceAuthorityService;
import org.dspace.core.Context;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.hateoas.Link;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
/**
* Controller for exposition of vocabularies entry details for the submission
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
*/
@Component(VocabularyRest.CATEGORY + "." + VocabularyEntryDetailsRest.NAME)
public class VocabularyEntryDetailsRestRepository extends DSpaceRestRepository<VocabularyEntryDetailsRest, String>
implements InitializingBean {
@Autowired
private ChoiceAuthorityService cas;
@Autowired
private AuthorityUtils authorityUtils;
@Autowired
private DiscoverableEndpointsService discoverableEndpointsService;
@Override
public void afterPropertiesSet() throws Exception {
String models = English.plural(VocabularyEntryDetailsRest.NAME);
discoverableEndpointsService.register(this, Arrays.asList(
new Link("/api/" + VocabularyRest.CATEGORY + "/" + models + "/search",
models + "-search")));
}
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@Override
public Page<VocabularyEntryDetailsRest> findAll(Context context, Pageable pageable) {
throw new RepositoryMethodNotImplementedException(ResourcePolicyRest.NAME, "findAll");
}
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@Override
public VocabularyEntryDetailsRest findOne(Context context, String name) {
String[] parts = StringUtils.split(name, ":", 2);
if (parts.length != 2) {
return null;
}
String vocabularyName = parts[0];
String vocabularyId = parts[1];
ChoiceAuthority source = cas.getChoiceAuthorityByAuthorityName(vocabularyName);
Choice choice = source.getChoice(vocabularyId, context.getCurrentLocale().toString());
return authorityUtils.convertEntryDetails(choice, vocabularyName, source.isHierarchical(),
utils.obtainProjection());
}
@SearchRestMethod(name = "top")
@PreAuthorize("hasAuthority('AUTHENTICATED')")
public Page<VocabularyEntryDetailsRest> findAllTop(@Parameter(value = "vocabulary", required = true)
String vocabularyId, Pageable pageable) {
Context context = obtainContext();
List<VocabularyEntryDetailsRest> results = new ArrayList<VocabularyEntryDetailsRest>();
ChoiceAuthority source = cas.getChoiceAuthorityByAuthorityName(vocabularyId);
if (source.isHierarchical()) {
Choices choices = cas.getTopChoices(vocabularyId, (int)pageable.getOffset(), pageable.getPageSize(),
context.getCurrentLocale().toString());
for (Choice value : choices.values) {
results.add(authorityUtils.convertEntryDetails(value, vocabularyId, source.isHierarchical(),
utils.obtainProjection()));
}
Page<VocabularyEntryDetailsRest> resources = new PageImpl<VocabularyEntryDetailsRest>(results, pageable,
choices.total);
return resources;
}
throw new LinkNotFoundException(VocabularyRest.CATEGORY, VocabularyEntryDetailsRest.NAME, vocabularyId);
}
@Override
public Class<VocabularyEntryDetailsRest> getDomainClass() {
return VocabularyEntryDetailsRest.class;
}
}

View File

@@ -0,0 +1,97 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.repository;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.dspace.app.rest.exception.UnprocessableEntityException;
import org.dspace.app.rest.model.VocabularyEntryRest;
import org.dspace.app.rest.model.VocabularyRest;
import org.dspace.app.rest.projection.Projection;
import org.dspace.app.rest.utils.AuthorityUtils;
import org.dspace.content.authority.Choice;
import org.dspace.content.authority.ChoiceAuthority;
import org.dspace.content.authority.Choices;
import org.dspace.content.authority.service.ChoiceAuthorityService;
import org.dspace.content.service.CollectionService;
import org.dspace.core.Context;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
/**
* Controller for exposition of authority services
*
* @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it)
*/
@Component(VocabularyRest.CATEGORY + "." + VocabularyRest.NAME + "." + VocabularyRest.ENTRIES)
public class VocabularyEntryLinkRepository extends AbstractDSpaceRestRepository
implements LinkRestRepository {
@Autowired
private ChoiceAuthorityService cas;
@Autowired
private CollectionService cs;
@Autowired
private AuthorityUtils authorityUtils;
@PreAuthorize("hasAuthority('AUTHENTICATED')")
public Page<VocabularyEntryRest> filter(@Nullable HttpServletRequest request, String name,
@Nullable Pageable optionalPageable, Projection projection) {
Context context = obtainContext();
String exact = request == null ? null : request.getParameter("exact");
String filter = request == null ? null : request.getParameter("filter");
String entryID = request == null ? null : request.getParameter("entryID");
if (StringUtils.isNotBlank(filter) && StringUtils.isNotBlank(entryID)) {
throw new IllegalArgumentException("the filter and entryID parameters are mutually exclusive");
}
Pageable pageable = utils.getPageable(optionalPageable);
List<VocabularyEntryRest> results = new ArrayList<>();
ChoiceAuthority ca = cas.getChoiceAuthorityByAuthorityName(name);
if (ca == null) {
throw new ResourceNotFoundException("the vocabulary named " + name + "doesn't exist");
}
if (!ca.isScrollable() && StringUtils.isBlank(filter) && StringUtils.isBlank(entryID)) {
throw new UnprocessableEntityException(
"one of filter or entryID parameter is required for not scrollable vocabularies");
}
Choices choices = null;
if (BooleanUtils.toBoolean(exact)) {
choices = ca.getBestMatch(filter, context.getCurrentLocale().toString());
} else if (StringUtils.isNotBlank(entryID)) {
Choice choice = ca.getChoice(entryID,
context.getCurrentLocale().toString());
if (choice != null) {
choices = new Choices(new Choice[] {choice}, 0, 1, Choices.CF_ACCEPTED, false);
} else {
choices = new Choices(false);
}
} else {
choices = ca.getMatches(filter, Math.toIntExact(pageable.getOffset()),
pageable.getPageSize(), context.getCurrentLocale().toString());
}
boolean storeAuthority = ca.storeAuthorityInMetadata();
for (Choice value : choices.values) {
results.add(authorityUtils.convertEntry(value, name, storeAuthority, projection));
}
return new PageImpl<>(results, pageable, choices.total);
}
}

View File

@@ -0,0 +1,112 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.repository;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.dspace.app.rest.Parameter;
import org.dspace.app.rest.SearchRestMethod;
import org.dspace.app.rest.exception.UnprocessableEntityException;
import org.dspace.app.rest.model.VocabularyRest;
import org.dspace.app.rest.projection.Projection;
import org.dspace.app.rest.utils.AuthorityUtils;
import org.dspace.content.Collection;
import org.dspace.content.MetadataField;
import org.dspace.content.authority.ChoiceAuthority;
import org.dspace.content.authority.service.ChoiceAuthorityService;
import org.dspace.content.service.CollectionService;
import org.dspace.content.service.MetadataFieldService;
import org.dspace.core.Context;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
/**
* Controller for exposition of vocabularies for the submission
*
* @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it)
* @author Andrea Bollini (andrea.bollini at 4science.it)
*/
@Component(VocabularyRest.CATEGORY + "." + VocabularyRest.NAME)
public class VocabularyRestRepository extends DSpaceRestRepository<VocabularyRest, String> {
@Autowired
private ChoiceAuthorityService cas;
@Autowired
private AuthorityUtils authorityUtils;
@Autowired
private CollectionService collectionService;
@Autowired
private MetadataFieldService metadataFieldService;
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@Override
public VocabularyRest findOne(Context context, String name) {
ChoiceAuthority source = cas.getChoiceAuthorityByAuthorityName(name);
return authorityUtils.convertAuthority(source, name, utils.obtainProjection());
}
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@Override
public Page<VocabularyRest> findAll(Context context, Pageable pageable) {
Set<String> authoritiesName = cas.getChoiceAuthoritiesNames();
List<VocabularyRest> results = new ArrayList<>();
Projection projection = utils.obtainProjection();
for (String authorityName : authoritiesName) {
ChoiceAuthority source = cas.getChoiceAuthorityByAuthorityName(authorityName);
VocabularyRest result = authorityUtils.convertAuthority(source, authorityName, projection);
results.add(result);
}
return utils.getPage(results, pageable);
}
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@SearchRestMethod(name = "byMetadataAndCollection")
public VocabularyRest findByMetadataAndCollection(
@Parameter(value = "metadata", required = true) String metadataField,
@Parameter(value = "collection", required = true) UUID collectionUuid) {
Collection collection = null;
MetadataField metadata = null;
String[] tokens = org.dspace.core.Utils.tokenize(metadataField);
try {
collection = collectionService.find(obtainContext(), collectionUuid);
metadata = metadataFieldService.findByElement(obtainContext(), tokens[0], tokens[1], tokens[2]);
} catch (SQLException e) {
throw new RuntimeException(
"A database error occurs retrieving the metadata and/or the collection information", e);
}
if (metadata == null) {
throw new UnprocessableEntityException(metadataField + " is not a valid metadata");
}
if (collection == null) {
throw new UnprocessableEntityException(collectionUuid + " is not a valid collection");
}
String authorityName = cas.getChoiceAuthorityName(tokens[0], tokens[1], tokens[2], collection);
ChoiceAuthority source = cas.getChoiceAuthorityByAuthorityName(authorityName);
return authorityUtils.convertAuthority(source, authorityName, utils.obtainProjection());
}
@Override
public Class<VocabularyRest> getDomainClass() {
return VocabularyRest.class;
}
}

View File

@@ -7,9 +7,11 @@
*/ */
package org.dspace.app.rest.utils; package org.dspace.app.rest.utils;
import org.apache.commons.lang3.StringUtils;
import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.converter.ConverterService;
import org.dspace.app.rest.model.AuthorityEntryRest; import org.dspace.app.rest.model.VocabularyEntryDetailsRest;
import org.dspace.app.rest.model.AuthorityRest; import org.dspace.app.rest.model.VocabularyEntryRest;
import org.dspace.app.rest.model.VocabularyRest;
import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.projection.Projection;
import org.dspace.content.authority.Choice; import org.dspace.content.authority.Choice;
import org.dspace.content.authority.ChoiceAuthority; import org.dspace.content.authority.ChoiceAuthority;
@@ -27,6 +29,8 @@ public class AuthorityUtils {
public static final String PRESENTATION_TYPE_LOOKUP = "lookup"; public static final String PRESENTATION_TYPE_LOOKUP = "lookup";
public static final String PRESENTATION_TYPE_AUTHORLOOKUP = "authorLookup";
public static final String PRESENTATION_TYPE_SUGGEST = "suggest"; public static final String PRESENTATION_TYPE_SUGGEST = "suggest";
public static final String RESERVED_KEYMAP_PARENT = "parent"; public static final String RESERVED_KEYMAP_PARENT = "parent";
@@ -39,11 +43,11 @@ public class AuthorityUtils {
public boolean isChoice(String schema, String element, String qualifier) { public boolean isChoice(String schema, String element, String qualifier) {
return cas.isChoicesConfigured(org.dspace.core.Utils.standardize(schema, element, qualifier, "_")); return cas.isChoicesConfigured(org.dspace.core.Utils.standardize(schema, element, qualifier, "_"), null);
} }
public String getAuthorityName(String schema, String element, String qualifier) { public String getAuthorityName(String schema, String element, String qualifier) {
return cas.getChoiceAuthorityName(schema, element, qualifier); return cas.getChoiceAuthorityName(schema, element, qualifier, null);
} }
public boolean isClosed(String schema, String element, String qualifier) { public boolean isClosed(String schema, String element, String qualifier) {
@@ -62,9 +66,45 @@ public class AuthorityUtils {
* @param projection the name of the projection to use, or {@code null}. * @param projection the name of the projection to use, or {@code null}.
* @return * @return
*/ */
public AuthorityEntryRest convertEntry(Choice choice, String authorityName, Projection projection) { public VocabularyEntryDetailsRest convertEntryDetails(Choice choice, String authorityName,
AuthorityEntryRest entry = converter.toRest(choice, projection); boolean isHierarchical, Projection projection) {
entry.setAuthorityName(authorityName); if (choice == null) {
return null;
}
VocabularyEntryDetailsRest entry = converter.toRest(choice, projection);
entry.setVocabularyName(authorityName);
entry.setId(authorityName + ":" + entry.getId());
entry.setInHierarchicalVocabulary(isHierarchical);
return entry;
}
/**
* This utility method is currently a workaround to enrich the REST object with
* information from the parent vocabulary that is not referenced by the Choice
* model
*
* @param choice the dspace-api choice to expose as vocabulary entry
* @param authorityName the name of the vocabulary
* @param storeAuthority <code>true</code> if the entry id should be exposed as
* an authority for storing it in the metadatavalue
* @param projection the rest projection to apply
* @return the vocabulary entry rest reppresentation of the provided choice
*/
public VocabularyEntryRest convertEntry(Choice choice, String authorityName, boolean storeAuthority,
Projection projection) {
if (choice == null) {
return null;
}
VocabularyEntryRest entry = new VocabularyEntryRest();
entry.setDisplay(choice.label);
entry.setValue(choice.value);
entry.setOtherInformation(choice.extras);
if (storeAuthority) {
entry.setAuthority(choice.authority);
}
if (StringUtils.isNotBlank(choice.authority)) {
entry.setVocabularyEntryDetailsRest(converter.toRest(choice, projection));
}
return entry; return entry;
} }
@@ -76,8 +116,8 @@ public class AuthorityUtils {
* @param projection the projecton to use. * @param projection the projecton to use.
* @return * @return
*/ */
public AuthorityRest convertAuthority(ChoiceAuthority source, String authorityName, Projection projection) { public VocabularyRest convertAuthority(ChoiceAuthority source, String authorityName, Projection projection) {
AuthorityRest result = converter.toRest(source, projection); VocabularyRest result = converter.toRest(source, projection);
result.setName(authorityName); result.setName(authorityName);
return result; return result;
} }

View File

@@ -28,7 +28,8 @@ public class RegexUtils {
* identifier (digits or uuid) * identifier (digits or uuid)
*/ */
public static final String REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG = "/{id:^(?!^\\d+$)" + public static final String REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG = "/{id:^(?!^\\d+$)" +
"(?!^[0-9a-fxA-FX]{8}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{12}$)[\\w+\\-\\.]+$+}"; "(?!^[0-9a-fxA-FX]{8}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{4}-[0-9a-fxA-FX]{12}$)"
+ "[\\w+\\-\\.:]+$+}";
/** /**
* Regular expression in the request mapping to accept number as identifier * Regular expression in the request mapping to accept number as identifier

View File

@@ -47,7 +47,6 @@ import org.apache.log4j.Logger;
import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.converter.ConverterService;
import org.dspace.app.rest.exception.PaginationException; import org.dspace.app.rest.exception.PaginationException;
import org.dspace.app.rest.exception.RepositoryNotFoundException; import org.dspace.app.rest.exception.RepositoryNotFoundException;
import org.dspace.app.rest.model.AuthorityRest;
import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.app.rest.model.BaseObjectRest;
import org.dspace.app.rest.model.CommunityRest; import org.dspace.app.rest.model.CommunityRest;
import org.dspace.app.rest.model.LinkRest; import org.dspace.app.rest.model.LinkRest;
@@ -58,6 +57,7 @@ import org.dspace.app.rest.model.ResourcePolicyRest;
import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.RestAddressableModel;
import org.dspace.app.rest.model.RestModel; import org.dspace.app.rest.model.RestModel;
import org.dspace.app.rest.model.VersionHistoryRest; import org.dspace.app.rest.model.VersionHistoryRest;
import org.dspace.app.rest.model.VocabularyRest;
import org.dspace.app.rest.model.hateoas.EmbeddedPage; import org.dspace.app.rest.model.hateoas.EmbeddedPage;
import org.dspace.app.rest.model.hateoas.HALResource; import org.dspace.app.rest.model.hateoas.HALResource;
import org.dspace.app.rest.projection.CompositeProjection; import org.dspace.app.rest.projection.CompositeProjection;
@@ -254,7 +254,7 @@ public class Utils {
return CommunityRest.NAME; return CommunityRest.NAME;
} }
if (modelPlural.equals("authorities")) { if (modelPlural.equals("authorities")) {
return AuthorityRest.NAME; return VocabularyRest.NAME;
} }
if (modelPlural.equals("resourcepolicies")) { if (modelPlural.equals("resourcepolicies")) {
return ResourcePolicyRest.NAME; return ResourcePolicyRest.NAME;
@@ -268,6 +268,9 @@ public class Utils {
if (StringUtils.equals(modelPlural, "properties")) { if (StringUtils.equals(modelPlural, "properties")) {
return PropertyRest.NAME; return PropertyRest.NAME;
} }
if (StringUtils.equals(modelPlural, "vocabularies")) {
return VocabularyRest.NAME;
}
return modelPlural.replaceAll("s$", ""); return modelPlural.replaceAll("s$", "");
} }

View File

@@ -1,233 +0,0 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.Date;
import java.util.UUID;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.dspace.app.rest.matcher.AuthorityEntryMatcher;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.authority.PersonAuthorityValue;
import org.dspace.authority.factory.AuthorityServiceFactory;
import org.dspace.content.authority.service.ChoiceAuthorityService;
import org.dspace.core.service.PluginService;
import org.dspace.services.ConfigurationService;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
/**
* This class handles all Authority related IT. It alters some config to run the tests, but it gets cleared again
* after every test
*/
public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest {
@Autowired
ConfigurationService configurationService;
@Autowired
private PluginService pluginService;
@Autowired
private ChoiceAuthorityService cas;
@Before
public void setup() throws Exception {
super.setUp();
configurationService.setProperty("plugin.named.org.dspace.content.authority.ChoiceAuthority",
"org.dspace.content.authority.SolrAuthority = SolrAuthorAuthority");
configurationService.setProperty("solr.authority.server",
"${solr.server}/authority");
configurationService.setProperty("choices.plugin.dc.contributor.author",
"SolrAuthorAuthority");
configurationService.setProperty("choices.presentation.dc.contributor.author",
"authorLookup");
configurationService.setProperty("authority.controlled.dc.contributor.author",
"true");
configurationService.setProperty("authority.author.indexer.field.1",
"dc.contributor.author");
// These clears have to happen so that the config is actually reloaded in those classes. This is needed for
// the properties that we're altering above and this is only used within the tests
pluginService.clearNamedPluginClasses();
cas.clearCache();
PersonAuthorityValue person1 = new PersonAuthorityValue();
person1.setId(String.valueOf(UUID.randomUUID()));
person1.setLastName("Shirasaka");
person1.setFirstName("Seiko");
person1.setValue("Shirasaka, Seiko");
person1.setField("dc_contributor_author");
person1.setLastModified(new Date());
person1.setCreationDate(new Date());
AuthorityServiceFactory.getInstance().getAuthorityIndexingService().indexContent(person1);
PersonAuthorityValue person2 = new PersonAuthorityValue();
person2.setId(String.valueOf(UUID.randomUUID()));
person2.setLastName("Miller");
person2.setFirstName("Tyler E");
person2.setValue("Miller, Tyler E");
person2.setField("dc_contributor_author");
person2.setLastModified(new Date());
person2.setCreationDate(new Date());
AuthorityServiceFactory.getInstance().getAuthorityIndexingService().indexContent(person2);
AuthorityServiceFactory.getInstance().getAuthorityIndexingService().commit();
}
@Test
public void correctSrscQueryTest() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(
get("/api/integration/authorities/srsc/entries")
.param("metadata", "dc.subject")
.param("query", "Research")
.param("size", "1000"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.page.totalElements", Matchers.is(26)));
}
@Test
public void noResultsSrscQueryTest() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(
get("/api/integration/authorities/srsc/entries")
.param("metadata", "dc.subject")
.param("query", "Research2")
.param("size", "1000"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.page.totalElements", Matchers.is(0)));
}
@Test
@Ignore
/**
* This functionality is currently broken, it returns all 22 values
*/
public void correctCommonTypesTest() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(
get("/api/integration/authorities/common_types/entries")
.param("metadata", "dc.type")
.param("query", "Book")
.param("size", "1000"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.page.totalElements", Matchers.is(2)));
}
@Test
public void correctSolrQueryTest() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(
get("/api/integration/authorities/SolrAuthorAuthority/entries")
.param("metadata", "dc.contributor.author")
.param("query", "Shirasaka")
.param("size", "1000"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.page.totalElements", Matchers.is(1)));
}
@Test
public void noResultsSolrQueryTest() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(
get("/api/integration/authorities/SolrAuthorAuthority/entries")
.param("metadata", "dc.contributor.author")
.param("query", "Smith")
.param("size", "1000"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.page.totalElements", Matchers.is(0)));
}
@Test
public void retrieveSrscValueTest() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
// When full projection is requested, response should include expected properties, links, and embeds.
getClient(token).perform(
get("/api/integration/authorities/srsc/entryValues/SCB1922").param("projection", "full"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", AuthorityEntryMatcher.matchFullEmbeds()))
.andExpect(jsonPath("$.page.totalElements", Matchers.is(1)));
}
@Test
public void noResultsSrscValueTest() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(
get("/api/integration/authorities/srsc/entryValues/DOESNTEXIST"))
.andExpect(status().isNotFound());
}
@Test
public void retrieveCommonTypesValueTest() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(
get("/api/integration/authorities/common_types/entryValues/Book").param("projection", "full"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.page.totalElements", Matchers.is(1)))
;
}
@Test
public void retrieveCommonTypesWithSpaceValueTest() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(
get("/api/integration/authorities/common_types/entryValues/Learning+Object"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.page.totalElements", Matchers.is(1)));
}
@Test
public void discoverableNestedLinkTest() throws Exception {
String token = getAuthToken(eperson.getEmail(), password);
getClient(token).perform(get("/api"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links",Matchers.allOf(
hasJsonPath("$.authorizations.href",
is("http://localhost/api/authz/authorizations")),
hasJsonPath("$.authorization-search.href",
is("http://localhost/api/authz/authorization/search"))
)));
}
@Test
public void retrieveSolrValueTest() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
SolrQuery query = new SolrQuery();
query.setQuery("*:*");
QueryResponse queryResponse = AuthorityServiceFactory.getInstance().getAuthoritySearchService().search(query);
String id = String.valueOf(queryResponse.getResults().get(0).getFieldValue("id"));
getClient(token).perform(
get("/api/integration/authorities/SolrAuthorAuthority/entryValues/" + id))
.andExpect(status().isOk())
.andExpect(jsonPath("$.page.totalElements", Matchers.is(1)));
}
@Override
public void destroy() throws Exception {
AuthorityServiceFactory.getInstance().getAuthorityIndexingService().cleanIndex();
super.destroy();
}
}

View File

@@ -49,7 +49,8 @@ public class RootRestResourceControllerIT extends AbstractControllerIntegrationT
//We expect the content type to be "application/hal+json;charset=UTF-8" //We expect the content type to be "application/hal+json;charset=UTF-8"
.andExpect(content().contentType(contentType)) .andExpect(content().contentType(contentType))
//Check that all required root links are present and that they are absolute //Check that all required root links are present and that they are absolute
.andExpect(jsonPath("$._links.authorities.href", startsWith(BASE_REST_SERVER_URL))) .andExpect(jsonPath("$._links.vocabularies.href", startsWith(BASE_REST_SERVER_URL)))
.andExpect(jsonPath("$._links.vocabularyEntryDetails.href", startsWith(BASE_REST_SERVER_URL)))
.andExpect(jsonPath("$._links.bitstreamformats.href", startsWith(BASE_REST_SERVER_URL))) .andExpect(jsonPath("$._links.bitstreamformats.href", startsWith(BASE_REST_SERVER_URL)))
.andExpect(jsonPath("$._links.bitstreams.href", startsWith(BASE_REST_SERVER_URL))) .andExpect(jsonPath("$._links.bitstreams.href", startsWith(BASE_REST_SERVER_URL)))
.andExpect(jsonPath("$._links.browses.href", startsWith(BASE_REST_SERVER_URL))) .andExpect(jsonPath("$._links.browses.href", startsWith(BASE_REST_SERVER_URL)))

View File

@@ -23,6 +23,9 @@ import org.dspace.app.rest.repository.SubmissionFormRestRepository;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.app.util.DCInputsReaderException; import org.dspace.app.util.DCInputsReaderException;
import org.dspace.builder.EPersonBuilder; import org.dspace.builder.EPersonBuilder;
import org.dspace.content.authority.DCInputAuthority;
import org.dspace.content.authority.service.ChoiceAuthorityService;
import org.dspace.core.service.PluginService;
import org.dspace.eperson.EPerson; import org.dspace.eperson.EPerson;
import org.dspace.services.ConfigurationService; import org.dspace.services.ConfigurationService;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
@@ -39,6 +42,10 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
private ConfigurationService configurationService; private ConfigurationService configurationService;
@Autowired @Autowired
private SubmissionFormRestRepository submissionFormRestRepository; private SubmissionFormRestRepository submissionFormRestRepository;
@Autowired
private PluginService pluginService;
@Autowired
private ChoiceAuthorityService cas;
@Test @Test
public void findAll() throws Exception { public void findAll() throws Exception {
@@ -56,15 +63,15 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.andExpect(status().isOk()) .andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8" //We expect the content type to be "application/hal+json;charset=UTF-8"
.andExpect(content().contentType(contentType)) .andExpect(content().contentType(contentType))
//The configuration file for the test env includes 3 forms //The configuration file for the test env includes 6 forms
.andExpect(jsonPath("$.page.size", is(20))) .andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", equalTo(4))) .andExpect(jsonPath("$.page.totalElements", equalTo(6)))
.andExpect(jsonPath("$.page.totalPages", equalTo(1))) .andExpect(jsonPath("$.page.totalPages", equalTo(1)))
.andExpect(jsonPath("$.page.number", is(0))) .andExpect(jsonPath("$.page.number", is(0)))
.andExpect( .andExpect(
jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "config/submissionforms"))) jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "config/submissionforms")))
//The array of submissionforms should have a size of 3 //The array of submissionforms should have a size of 6
.andExpect(jsonPath("$._embedded.submissionforms", hasSize(equalTo(4)))) .andExpect(jsonPath("$._embedded.submissionforms", hasSize(equalTo(6))))
; ;
} }
@@ -75,12 +82,12 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().contentType(contentType)) .andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.page.size", is(20))) .andExpect(jsonPath("$.page.size", is(20)))
.andExpect(jsonPath("$.page.totalElements", equalTo(4))) .andExpect(jsonPath("$.page.totalElements", equalTo(6)))
.andExpect(jsonPath("$.page.totalPages", equalTo(1))) .andExpect(jsonPath("$.page.totalPages", equalTo(1)))
.andExpect(jsonPath("$.page.number", is(0))) .andExpect(jsonPath("$.page.number", is(0)))
.andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL .andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL
+ "config/submissionforms"))) + "config/submissionforms")))
.andExpect(jsonPath("$._embedded.submissionforms", hasSize(equalTo(4)))); .andExpect(jsonPath("$._embedded.submissionforms", hasSize(equalTo(6))));
} }
@Test @Test
@@ -152,6 +159,121 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
"col-sm-8","dc.publisher")))); "col-sm-8","dc.publisher"))));
} }
@Test
public void findFieldWithAuthorityConfig() throws Exception {
configurationService.setProperty("plugin.named.org.dspace.content.authority.ChoiceAuthority",
new String[] {
"org.dspace.content.authority.SolrAuthority = SolrAuthorAuthority",
"org.dspace.content.authority.SolrAuthority = SolrEditorAuthority",
"org.dspace.content.authority.SolrAuthority = SolrSubjectAuthority"
});
configurationService.setProperty("solr.authority.server",
"${solr.server}/authority");
configurationService.setProperty("choices.plugin.dc.contributor.author",
"SolrAuthorAuthority");
configurationService.setProperty("choices.presentation.dc.contributor.author",
"suggest");
configurationService.setProperty("authority.controlled.dc.contributor.author",
"true");
configurationService.setProperty("authority.author.indexer.field.1",
"dc.contributor.author");
configurationService.setProperty("choices.plugin.dc.contributor.editor",
"SolrEditorAuthority");
configurationService.setProperty("choices.presentation.dc.contributor.editor",
"authorLookup");
configurationService.setProperty("authority.controlled.dc.contributor.editor",
"true");
configurationService.setProperty("authority.author.indexer.field.2",
"dc.contributor.editor");
configurationService.setProperty("choices.plugin.dc.subject",
"SolrSubjectAuthority");
configurationService.setProperty("choices.presentation.dc.subject",
"lookup");
configurationService.setProperty("authority.controlled.dc.subject",
"true");
configurationService.setProperty("authority.author.indexer.field.3",
"dc.subject");
// These clears have to happen so that the config is actually reloaded in those classes. This is needed for
// the properties that we're altering above and this is only used within the tests
submissionFormRestRepository.reload();
DCInputAuthority.reset();
pluginService.clearNamedPluginClasses();
cas.clearCache();
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(get("/api/config/submissionforms/sampleauthority"))
//The status has to be 200 OK
.andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8"
.andExpect(content().contentType(contentType))
//Check that the JSON root matches the expected "sampleauthority" input forms
.andExpect(jsonPath("$.id", is("sampleauthority")))
.andExpect(jsonPath("$.name", is("sampleauthority")))
.andExpect(jsonPath("$.type", is("submissionform")))
.andExpect(jsonPath("$._links.self.href", Matchers
.startsWith(REST_SERVER_URL + "config/submissionforms/sampleauthority")))
// our test configuration include the dc.contributor.author, dc.contributor.editor and
// dc.subject fields with in separate rows all linked to an authority with different
// presentation modes (suggestion, name-lookup, lookup)
.andExpect(jsonPath("$.rows[0].fields", contains(
SubmissionFormFieldMatcher.matchFormFieldDefinition("onebox", "Author",
null, true,
"Author field that can be associated with an authority providing suggestion",
null, "dc.contributor.author", "SolrAuthorAuthority")
)))
.andExpect(jsonPath("$.rows[1].fields", contains(
SubmissionFormFieldMatcher.matchFormFieldDefinition("lookup-name", "Editor",
null, false,
"Editor field that can be associated with an authority "
+ "providing the special name lookup",
null, "dc.contributor.editor", "SolrEditorAuthority")
)))
.andExpect(jsonPath("$.rows[2].fields", contains(
SubmissionFormFieldMatcher.matchFormFieldDefinition("lookup", "Subject",
null, true,
"Subject field that can be associated with an authority providing lookup",
null, "dc.subject", "SolrSubjectAuthority")
)))
;
// we need to force a reload of the config now to be able to reload also the cache of the other
// authority related services. As this is needed just by this test method it is more efficient do it
// here instead that force these reload for each method extending the destroy method
configurationService.reloadConfig();
submissionFormRestRepository.reload();
DCInputAuthority.reset();
pluginService.clearNamedPluginClasses();
cas.clearCache();
}
@Test
public void findFieldWithValuePairsConfig() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(get("/api/config/submissionforms/traditionalpageone"))
//The status has to be 200 OK
.andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8"
.andExpect(content().contentType(contentType))
//Check that the JSON root matches the expected "traditionalpageone" input forms
.andExpect(jsonPath("$.id", is("traditionalpageone")))
.andExpect(jsonPath("$.name", is("traditionalpageone")))
.andExpect(jsonPath("$.type", is("submissionform")))
.andExpect(jsonPath("$._links.self.href", Matchers
.startsWith(REST_SERVER_URL + "config/submissionforms/traditionalpageone")))
// our test configuration include the dc.type field with a value pair in the 8th row
.andExpect(jsonPath("$.rows[7].fields", contains(
SubmissionFormFieldMatcher.matchFormFieldDefinition("dropdown", "Type",
null, true,
"Select the type(s) of content of the item. To select more than one value in the " +
"list, you may have to hold down the \"CTRL\" or \"Shift\" key.",
null, "dc.type", "common_types")
)))
;
}
@Test @Test
public void findOpenRelationshipConfig() throws Exception { public void findOpenRelationshipConfig() throws Exception {
String token = getAuthToken(admin.getEmail(), password); String token = getAuthToken(admin.getEmail(), password);
@@ -203,8 +325,14 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
public void languageSupportTest() throws Exception { public void languageSupportTest() throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
String[] supportedLanguage = {"it","uk"}; String[] supportedLanguage = {"it","uk"};
configurationService.setProperty("default.locale","it");
configurationService.setProperty("webui.supported.locales",supportedLanguage); configurationService.setProperty("webui.supported.locales",supportedLanguage);
// These clears have to happen so that the config is actually reloaded in those classes. This is needed for
// the properties that we're altering above and this is only used within the tests
submissionFormRestRepository.reload(); submissionFormRestRepository.reload();
DCInputAuthority.reset();
pluginService.clearNamedPluginClasses();
cas.clearCache();
Locale uk = new Locale("uk"); Locale uk = new Locale("uk");
Locale it = new Locale("it"); Locale it = new Locale("it");
@@ -234,7 +362,8 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
"Selezionare la lingua del contenuto principale dell'item." "Selezionare la lingua del contenuto principale dell'item."
+ " Se la lingua non compare nell'elenco, selezionare (Altro)." + " Se la lingua non compare nell'elenco, selezionare (Altro)."
+ " Se il contenuto non ha davvero una lingua" + " Se il contenuto non ha davvero una lingua"
+ " (ad esempio, se è un set di dati o un'immagine) selezionare (N/A)", "dc.language.iso")))); + " (ad esempio, se è un set di dati o un'immagine) selezionare (N/A)",
null, "dc.language.iso", "common_iso_languages"))));
// user select ukranian language // user select ukranian language
getClient(tokenEperson).perform(get("/api/config/submissionforms/languagetest").locale(uk)) getClient(tokenEperson).perform(get("/api/config/submissionforms/languagetest").locale(uk))
@@ -256,9 +385,8 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.matchFormFieldDefinition("dropdown", "Мова", null, false, .matchFormFieldDefinition("dropdown", "Мова", null, false,
"Виберiть мову головного змiсту файлу, як що мови немає у списку, вибрати (Iнша)." "Виберiть мову головного змiсту файлу, як що мови немає у списку, вибрати (Iнша)."
+ " Як що вмiст вайлу не є текстовим, наприклад є фотографiєю, тодi вибрати (N/A)", + " Як що вмiст вайлу не є текстовим, наприклад є фотографiєю, тодi вибрати (N/A)",
"dc.language.iso")))); null, "dc.language.iso", "common_iso_languages"))));
resetLocalesConfiguration();
resetLocalesConfiguration();
} }
@Test @Test
@@ -266,8 +394,14 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
String[] supportedLanguage = {"it","uk"}; String[] supportedLanguage = {"it","uk"};
configurationService.setProperty("default.locale","it");
configurationService.setProperty("webui.supported.locales",supportedLanguage); configurationService.setProperty("webui.supported.locales",supportedLanguage);
// These clears have to happen so that the config is actually reloaded in those classes. This is needed for
// the properties that we're altering above and this is only used within the tests
submissionFormRestRepository.reload(); submissionFormRestRepository.reload();
DCInputAuthority.reset();
pluginService.clearNamedPluginClasses();
cas.clearCache();
EPerson epersonIT = EPersonBuilder.createEPerson(context) EPerson epersonIT = EPersonBuilder.createEPerson(context)
.withEmail("epersonIT@example.com") .withEmail("epersonIT@example.com")
@@ -307,7 +441,8 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
"Selezionare la lingua del contenuto principale dell'item." "Selezionare la lingua del contenuto principale dell'item."
+ " Se la lingua non compare nell'elenco, selezionare (Altro)." + " Se la lingua non compare nell'elenco, selezionare (Altro)."
+ " Se il contenuto non ha davvero una lingua" + " Se il contenuto non ha davvero una lingua"
+ " (ad esempio, se è un set di dati o un'immagine) selezionare (N/A)", "dc.language.iso")))); + " (ad esempio, se è un set di dati o un'immagine) selezionare (N/A)",
null, "dc.language.iso", "common_iso_languages"))));
// user with ukranian prefer language // user with ukranian prefer language
getClient(tokenEpersonUK).perform(get("/api/config/submissionforms/languagetest")) getClient(tokenEpersonUK).perform(get("/api/config/submissionforms/languagetest"))
@@ -329,9 +464,8 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.matchFormFieldDefinition("dropdown", "Мова", null, false, .matchFormFieldDefinition("dropdown", "Мова", null, false,
"Виберiть мову головного змiсту файлу, як що мови немає у списку, вибрати (Iнша)." "Виберiть мову головного змiсту файлу, як що мови немає у списку, вибрати (Iнша)."
+ " Як що вмiст вайлу не є текстовим, наприклад є фотографiєю, тодi вибрати (N/A)", + " Як що вмiст вайлу не є текстовим, наприклад є фотографiєю, тодi вибрати (N/A)",
"dc.language.iso")))); null, "dc.language.iso", "common_iso_languages"))));
resetLocalesConfiguration();
resetLocalesConfiguration();
} }
@Test @Test
@@ -339,8 +473,14 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
String[] supportedLanguage = {"it","uk"}; String[] supportedLanguage = {"it","uk"};
configurationService.setProperty("default.locale","it");
configurationService.setProperty("webui.supported.locales",supportedLanguage); configurationService.setProperty("webui.supported.locales",supportedLanguage);
// These clears have to happen so that the config is actually reloaded in those classes. This is needed for
// the properties that we're altering above and this is only used within the tests
submissionFormRestRepository.reload(); submissionFormRestRepository.reload();
DCInputAuthority.reset();
pluginService.clearNamedPluginClasses();
cas.clearCache();
Locale it = new Locale("it"); Locale it = new Locale("it");
@@ -375,9 +515,9 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
"Selezionare la lingua del contenuto principale dell'item." "Selezionare la lingua del contenuto principale dell'item."
+ " Se la lingua non compare nell'elenco, selezionare (Altro)." + " Se la lingua non compare nell'elenco, selezionare (Altro)."
+ " Se il contenuto non ha davvero una lingua" + " Se il contenuto non ha davvero una lingua"
+ " (ad esempio, se è un set di dati o un'immagine) selezionare (N/A)", "dc.language.iso")))); + " (ad esempio, se è un set di dati o un'immagine) selezionare (N/A)",
null, "dc.language.iso", "common_iso_languages"))));
resetLocalesConfiguration(); resetLocalesConfiguration();
} }
@Test @Test
@@ -387,7 +527,12 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
String[] supportedLanguage = {"it","uk"}; String[] supportedLanguage = {"it","uk"};
configurationService.setProperty("default.locale","it"); configurationService.setProperty("default.locale","it");
configurationService.setProperty("webui.supported.locales",supportedLanguage); configurationService.setProperty("webui.supported.locales",supportedLanguage);
// These clears have to happen so that the config is actually reloaded in those classes. This is needed for
// the properties that we're altering above and this is only used within the tests
submissionFormRestRepository.reload(); submissionFormRestRepository.reload();
DCInputAuthority.reset();
pluginService.clearNamedPluginClasses();
cas.clearCache();
context.restoreAuthSystemState(); context.restoreAuthSystemState();
@@ -407,8 +552,7 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.matchFormFieldDefinition("onebox", "Titolo", .matchFormFieldDefinition("onebox", "Titolo",
"\u00C8 necessario inserire un titolo principale per questo item", false, "\u00C8 necessario inserire un titolo principale per questo item", false,
"Inserisci titolo principale di questo item", "dc.title")))); "Inserisci titolo principale di questo item", "dc.title"))));
resetLocalesConfiguration();
resetLocalesConfiguration();
} }
@Test @Test
@@ -417,7 +561,12 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
String[] supportedLanguage = {"it","uk","en"}; String[] supportedLanguage = {"it","uk","en"};
configurationService.setProperty("default.locale","en"); configurationService.setProperty("default.locale","en");
configurationService.setProperty("webui.supported.locales",supportedLanguage); configurationService.setProperty("webui.supported.locales",supportedLanguage);
// These clears have to happen so that the config is actually reloaded in those classes. This is needed for
// the properties that we're altering above and this is only used within the tests
submissionFormRestRepository.reload(); submissionFormRestRepository.reload();
DCInputAuthority.reset();
pluginService.clearNamedPluginClasses();
cas.clearCache();
context.restoreAuthSystemState(); context.restoreAuthSystemState();
@@ -446,5 +595,8 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
configurationService.setProperty("default.locale","en"); configurationService.setProperty("default.locale","en");
configurationService.setProperty("webui.supported.locales",null); configurationService.setProperty("webui.supported.locales",null);
submissionFormRestRepository.reload(); submissionFormRestRepository.reload();
DCInputAuthority.reset();
pluginService.clearNamedPluginClasses();
cas.clearCache();
} }
} }

View File

@@ -0,0 +1,442 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.UUID;
import org.dspace.app.rest.matcher.VocabularyEntryDetailsMatcher;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.junit.Test;
/**
*
*
* @author Mykhaylo Boychuk (4science.it)
*/
public class VocabularyEntryDetailsIT extends AbstractControllerIntegrationTest {
@Test
public void discoverableNestedLinkTest() throws Exception {
String token = getAuthToken(eperson.getEmail(), password);
getClient(token).perform(get("/api"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links",Matchers.allOf(
hasJsonPath("$.vocabularyEntryDetails.href",
is("http://localhost/api/submission/vocabularyEntryDetails")),
hasJsonPath("$.vocabularyEntryDetails-search.href",
is("http://localhost/api/submission/vocabularyEntryDetails/search"))
)));
}
@Test
public void findAllTest() throws Exception {
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/submission/vocabularyEntryDetails"))
.andExpect(status()
.isMethodNotAllowed());
}
@Test
public void findOneTest() throws Exception {
String idAuthority = "srsc:SCB110";
String token = getAuthToken(eperson.getEmail(), password);
getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + idAuthority))
.andExpect(status().isOk())
.andExpect(jsonPath("$",
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB110", "Religion/Theology",
"Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology")))
.andExpect(jsonPath("$.selectable", is(true)))
.andExpect(jsonPath("$.otherInformation.id", is("SCB110")))
.andExpect(jsonPath("$.otherInformation.note", is("Religionsvetenskap/Teologi")))
.andExpect(jsonPath("$.otherInformation.parent",
is("Research Subject Categories::HUMANITIES and RELIGION")))
.andExpect(jsonPath("$._links.parent.href",
endsWith("api/submission/vocabularyEntryDetails/srsc:SCB110/parent")))
.andExpect(jsonPath("$._links.children.href",
endsWith("api/submission/vocabularyEntryDetails/srsc:SCB110/children")));
}
@Test
public void findOneUnauthorizedTest() throws Exception {
String idAuthority = "srsc:SCB110";
getClient().perform(get("/api/submission/vocabularyEntryDetails/" + idAuthority))
.andExpect(status().isUnauthorized());
}
@Test
public void findOneNotFoundTest() throws Exception {
String idAuthority = "srsc:not-existing";
String token = getAuthToken(eperson.getEmail(), password);
getClient(token).perform(get("/api/submission/vocabularyEntryDetails/" + idAuthority))
.andExpect(status().isNotFound());
// try with a special id missing only the entry-id part
getClient(token).perform(get("/api/submission/vocabularyEntryDetails/srsc:"))
.andExpect(status().isNotFound());
// try to retrieve the xml root that is not a entry itself
getClient(token).perform(get("/api/submission/vocabularyEntryDetails/srsc:ResearchSubjectCategories"))
.andExpect(status().isNotFound());
}
@Test
public void srscSearchTopTest() throws Exception {
String tokenAdmin = getAuthToken(admin.getEmail(), password);
String tokenEPerson = getAuthToken(eperson.getEmail(), password);
getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/search/top")
.param("vocabulary", "srsc"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder(
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB11", "HUMANITIES and RELIGION",
"Research Subject Categories::HUMANITIES and RELIGION"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB12", "LAW/JURISPRUDENCE",
"Research Subject Categories::LAW/JURISPRUDENCE"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB13", "SOCIAL SCIENCES",
"Research Subject Categories::SOCIAL SCIENCES"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB14", "MATHEMATICS",
"Research Subject Categories::MATHEMATICS"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB15", "NATURAL SCIENCES",
"Research Subject Categories::NATURAL SCIENCES"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB16", "TECHNOLOGY",
"Research Subject Categories::TECHNOLOGY"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB17",
"FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING",
"Research Subject Categories::FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB18", "MEDICINE",
"Research Subject Categories::MEDICINE"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB19", "ODONTOLOGY",
"Research Subject Categories::ODONTOLOGY"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB21", "PHARMACY",
"Research Subject Categories::PHARMACY"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB22", "VETERINARY MEDICINE",
"Research Subject Categories::VETERINARY MEDICINE"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS",
"Research Subject Categories::INTERDISCIPLINARY RESEARCH AREAS")
)))
.andExpect(jsonPath("$.page.totalElements", Matchers.is(12)));
getClient(tokenEPerson).perform(get("/api/submission/vocabularyEntryDetails/search/top")
.param("vocabulary", "srsc"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder(
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB11", "HUMANITIES and RELIGION",
"Research Subject Categories::HUMANITIES and RELIGION"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB12", "LAW/JURISPRUDENCE",
"Research Subject Categories::LAW/JURISPRUDENCE"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB13", "SOCIAL SCIENCES",
"Research Subject Categories::SOCIAL SCIENCES"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB14", "MATHEMATICS",
"Research Subject Categories::MATHEMATICS"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB15", "NATURAL SCIENCES",
"Research Subject Categories::NATURAL SCIENCES"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB16", "TECHNOLOGY",
"Research Subject Categories::TECHNOLOGY"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB17",
"FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING",
"Research Subject Categories::FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB18", "MEDICINE",
"Research Subject Categories::MEDICINE"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB19", "ODONTOLOGY",
"Research Subject Categories::ODONTOLOGY"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB21", "PHARMACY",
"Research Subject Categories::PHARMACY"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB22", "VETERINARY MEDICINE",
"Research Subject Categories::VETERINARY MEDICINE"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS",
"Research Subject Categories::INTERDISCIPLINARY RESEARCH AREAS")
)))
.andExpect(jsonPath("$.page.totalElements", Matchers.is(12)));
}
@Test
public void srscSearchFirstLevel_MATHEMATICS_Test() throws Exception {
String tokenAdmin = getAuthToken(admin.getEmail(), password);
getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB14/children"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder(
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1401",
"Algebra, geometry and mathematical analysis",
"Research Subject Categories::MATHEMATICS::Algebra, geometry and mathematical analysis"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1402", "Applied mathematics",
"Research Subject Categories::MATHEMATICS::Applied mathematics"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1409", "Other mathematics",
"Research Subject Categories::MATHEMATICS::Other mathematics")
)))
.andExpect(jsonPath("$._embedded.children[*].otherInformation.parent",
Matchers.everyItem(is("Research Subject Categories::MATHEMATICS"))))
.andExpect(jsonPath("$.page.totalElements", Matchers.is(3)));
}
@Test
public void srscSearchTopPaginationTest() throws Exception {
String tokenAdmin = getAuthToken(admin.getEmail(), password);
getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/search/top")
.param("vocabulary", "srsc")
.param("page", "0")
.param("size", "5"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder(
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB11", "HUMANITIES and RELIGION",
"Research Subject Categories::HUMANITIES and RELIGION"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB12", "LAW/JURISPRUDENCE",
"Research Subject Categories::LAW/JURISPRUDENCE"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB13", "SOCIAL SCIENCES",
"Research Subject Categories::SOCIAL SCIENCES"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB14", "MATHEMATICS",
"Research Subject Categories::MATHEMATICS"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB15", "NATURAL SCIENCES",
"Research Subject Categories::NATURAL SCIENCES")
)))
.andExpect(jsonPath("$.page.totalElements", is(12)))
.andExpect(jsonPath("$.page.totalPages", is(3)))
.andExpect(jsonPath("$.page.number", is(0)));
//second page
getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/search/top")
.param("vocabulary", "srsc")
.param("page", "1")
.param("size", "5"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder(
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB16", "TECHNOLOGY",
"Research Subject Categories::TECHNOLOGY"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB17",
"FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING",
"Research Subject Categories::FORESTRY, AGRICULTURAL SCIENCES and LANDSCAPE PLANNING"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB18", "MEDICINE",
"Research Subject Categories::MEDICINE"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB19", "ODONTOLOGY",
"Research Subject Categories::ODONTOLOGY"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB21", "PHARMACY",
"Research Subject Categories::PHARMACY")
)))
.andExpect(jsonPath("$.page.totalElements", is(12)))
.andExpect(jsonPath("$.page.totalPages", is(3)))
.andExpect(jsonPath("$.page.number", is(1)));
// third page
getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/search/top")
.param("vocabulary", "srsc")
.param("page", "2")
.param("size", "5"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.vocabularyEntryDetails", Matchers.containsInAnyOrder(
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB22", "VETERINARY MEDICINE",
"Research Subject Categories::VETERINARY MEDICINE"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB23", "INTERDISCIPLINARY RESEARCH AREAS",
"Research Subject Categories::INTERDISCIPLINARY RESEARCH AREAS")
)))
.andExpect(jsonPath("$.page.totalElements", is(12)))
.andExpect(jsonPath("$.page.totalPages", is(3)))
.andExpect(jsonPath("$.page.number", is(2)));
}
@Test
public void searchTopBadRequestTest() throws Exception {
String tokenAdmin = getAuthToken(admin.getEmail(), password);
getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/search/top")
.param("vocabulary", UUID.randomUUID().toString()))
.andExpect(status().isBadRequest());
}
@Test
public void searchTopUnauthorizedTest() throws Exception {
getClient().perform(get("/api/submission/vocabularyEntryDetails/search/top")
.param("vocabulary", "srsc:SCB16"))
.andExpect(status().isUnauthorized());
}
@Test
public void srscSearchByParentFirstLevelPaginationTest() throws Exception {
String token = getAuthToken(eperson.getEmail(), password);
// first page
getClient(token).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB14/children")
.param("page", "0")
.param("size", "2"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder(
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1401",
"Algebra, geometry and mathematical analysis",
"Research Subject Categories::MATHEMATICS::Algebra, geometry and mathematical analysis"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1402", "Applied mathematics",
"Research Subject Categories::MATHEMATICS::Applied mathematics")
)))
.andExpect(jsonPath("$.page.totalElements", is(3)))
.andExpect(jsonPath("$.page.totalPages", is(2)))
.andExpect(jsonPath("$.page.number", is(0)));
// second page
getClient(token).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB14/children")
.param("page", "1")
.param("size", "2"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.children", Matchers.contains(
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:SCB1409", "Other mathematics",
"Research Subject Categories::MATHEMATICS::Other mathematics")
)))
.andExpect(jsonPath("$.page.totalElements", is(3)))
.andExpect(jsonPath("$.page.totalPages", is(2)))
.andExpect(jsonPath("$.page.number", is(1)));
}
@Test
public void srscSearchByParentSecondLevel_Applied_mathematics_Test() throws Exception {
String token = getAuthToken(eperson.getEmail(), password);
getClient(token).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB1402/children"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.children", Matchers.containsInAnyOrder(
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140202", "Numerical analysis",
"Research Subject Categories::MATHEMATICS::Applied mathematics::Numerical analysis"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140203", "Mathematical statistics",
"Research Subject Categories::MATHEMATICS::Applied mathematics::Mathematical statistics"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140204", "Optimization, systems theory",
"Research Subject Categories::MATHEMATICS::Applied mathematics::Optimization, systems theory"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR140205", "Theoretical computer science",
"Research Subject Categories::MATHEMATICS::Applied mathematics::Theoretical computer science")
)))
.andExpect(jsonPath("$.page.totalElements", Matchers.is(4)));
}
@Test
public void srscSearchByParentEmptyTest() throws Exception {
String tokenAdmin = getAuthToken(admin.getEmail(), password);
getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/srsc:VR140202/children"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.page.totalElements", Matchers.is(0)));
}
@Test
public void srscSearchByParentWrongIdTest() throws Exception {
String tokenAdmin = getAuthToken(admin.getEmail(), password);
getClient(tokenAdmin).perform(get("/api/submission/vocabularyEntryDetails/"
+ UUID.randomUUID() + "/children"))
.andExpect(status().isBadRequest());
}
@Test
public void srscSearchTopUnauthorizedTest() throws Exception {
getClient().perform(get("/api/submission/vocabularyEntryDetails/search/top")
.param("vocabulary", "srsc"))
.andExpect(status().isUnauthorized());
}
@Test
public void findParentByChildTest() throws Exception {
String tokenEperson = getAuthToken(eperson.getEmail(), password);
getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB180/parent"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", is(
VocabularyEntryDetailsMatcher.matchAuthorityEntry(
"srsc:SCB18", "MEDICINE","Research Subject Categories::MEDICINE")
)));
}
@Test
public void findParentByChildBadRequestTest() throws Exception {
String tokenEperson = getAuthToken(eperson.getEmail(), password);
getClient(tokenEperson).perform(get("/api/submission/vocabularyEntryDetails/" + UUID.randomUUID() + "/parent"))
.andExpect(status().isBadRequest());
}
@Test
public void findParentByChildUnauthorizedTest() throws Exception {
getClient().perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB180/parent"))
.andExpect(status().isUnauthorized());
}
@Test
public void findParentTopTest() throws Exception {
String tokenEperson = getAuthToken(eperson.getEmail(), password);
getClient(tokenEperson)
.perform(get("/api/submission/vocabularyEntryDetails/srsc:SCB11/parent"))
.andExpect(status().isNoContent());
}
@Test
public void srscProjectionTest() throws Exception {
String tokenAdmin = getAuthToken(admin.getEmail(), password);
getClient(tokenAdmin).perform(
get("/api/submission/vocabularyEntryDetails/srsc:SCB110").param("projection", "full"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.parent",
VocabularyEntryDetailsMatcher.matchAuthorityEntry(
"srsc:SCB11", "HUMANITIES and RELIGION",
"Research Subject Categories::HUMANITIES and RELIGION")))
.andExpect(jsonPath("$._embedded.children._embedded.children", matchAllSrscSC110Children()))
.andExpect(jsonPath("$._embedded.children._embedded.children[*].otherInformation.parent",
Matchers.everyItem(
is("Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology"))));
getClient(tokenAdmin).perform(
get("/api/submission/vocabularyEntryDetails/srsc:SCB110").param("embed", "children"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.children._embedded.children", matchAllSrscSC110Children()))
.andExpect(jsonPath("$._embedded.children._embedded.children[*].otherInformation.parent",
Matchers.everyItem(
is("Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology"))));
getClient(tokenAdmin).perform(
get("/api/submission/vocabularyEntryDetails/srsc:SCB110").param("embed", "parent"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.parent",
VocabularyEntryDetailsMatcher.matchAuthorityEntry(
"srsc:SCB11", "HUMANITIES and RELIGION",
"Research Subject Categories::HUMANITIES and RELIGION")));
}
private Matcher<Iterable<? extends Object>> matchAllSrscSC110Children() {
return Matchers.containsInAnyOrder(
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110102",
"History of religion",
"Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::History of religion"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110103",
"Church studies",
"Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Church studies"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110104",
"Missionary studies",
"Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Missionary studies"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110105",
"Systematic theology",
"Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Systematic theology"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110106",
"Islamology",
"Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Islamology"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110107",
"Faith and reason",
"Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Faith and reason"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110108",
"Sociology of religion",
"Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Sociology of religion"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110109",
"Psychology of religion",
"Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Psychology of religion"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110110",
"Philosophy of religion",
"Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Philosophy of religion"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110111",
"New Testament exegesis",
"Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::New Testament exegesis"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110112",
"Old Testament exegesis",
"Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Old Testament exegesis"),
VocabularyEntryDetailsMatcher.matchAuthorityEntry("srsc:VR110113",
"Dogmatics with symbolics",
"Research Subject Categories::HUMANITIES and RELIGION::Religion/Theology::Dogmatics with symbolics")
);
}
}

View File

@@ -0,0 +1,394 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.Date;
import java.util.Locale;
import java.util.UUID;
import org.dspace.app.rest.matcher.VocabularyMatcher;
import org.dspace.app.rest.repository.SubmissionFormRestRepository;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.authority.PersonAuthorityValue;
import org.dspace.authority.factory.AuthorityServiceFactory;
import org.dspace.builder.CollectionBuilder;
import org.dspace.builder.CommunityBuilder;
import org.dspace.content.Collection;
import org.dspace.content.authority.DCInputAuthority;
import org.dspace.content.authority.service.ChoiceAuthorityService;
import org.dspace.core.service.PluginService;
import org.dspace.services.ConfigurationService;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
/**
* This class handles all Authority related IT. It alters some config to run the tests, but it gets cleared again
* after every test
*/
public class VocabularyRestRepositoryIT extends AbstractControllerIntegrationTest {
@Autowired
ConfigurationService configurationService;
@Autowired
private SubmissionFormRestRepository submissionFormRestRepository;
@Autowired
private PluginService pluginService;
@Autowired
private ChoiceAuthorityService cas;
@Before
public void setup() throws Exception {
super.setUp();
configurationService.setProperty("plugin.named.org.dspace.content.authority.ChoiceAuthority",
"org.dspace.content.authority.SolrAuthority = SolrAuthorAuthority");
configurationService.setProperty("solr.authority.server",
"${solr.server}/authority");
configurationService.setProperty("choices.plugin.dc.contributor.author",
"SolrAuthorAuthority");
configurationService.setProperty("choices.presentation.dc.contributor.author",
"authorLookup");
configurationService.setProperty("authority.controlled.dc.contributor.author",
"true");
configurationService.setProperty("authority.author.indexer.field.1",
"dc.contributor.author");
// These clears have to happen so that the config is actually reloaded in those classes. This is needed for
// the properties that we're altering above and this is only used within the tests
DCInputAuthority.reset();
pluginService.clearNamedPluginClasses();
cas.clearCache();
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context).withName("A parent community for all our test")
.build();
context.restoreAuthSystemState();
PersonAuthorityValue person1 = new PersonAuthorityValue();
person1.setId(String.valueOf(UUID.randomUUID()));
person1.setLastName("Shirasaka");
person1.setFirstName("Seiko");
person1.setValue("Shirasaka, Seiko");
person1.setField("dc_contributor_author");
person1.setLastModified(new Date());
person1.setCreationDate(new Date());
AuthorityServiceFactory.getInstance().getAuthorityIndexingService().indexContent(person1);
PersonAuthorityValue person2 = new PersonAuthorityValue();
person2.setId(String.valueOf(UUID.randomUUID()));
person2.setLastName("Miller");
person2.setFirstName("Tyler E");
person2.setValue("Miller, Tyler E");
person2.setField("dc_contributor_author");
person2.setLastModified(new Date());
person2.setCreationDate(new Date());
AuthorityServiceFactory.getInstance().getAuthorityIndexingService().indexContent(person2);
AuthorityServiceFactory.getInstance().getAuthorityIndexingService().commit();
}
@Override
@After
// We need to cleanup the authorities cache once than the configuration has been restored
public void destroy() throws Exception {
super.destroy();
DCInputAuthority.reset();
pluginService.clearNamedPluginClasses();
cas.clearCache();
}
@Test
public void findAllTest() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(get("/api/submission/vocabularies"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.vocabularies", Matchers.containsInAnyOrder(
VocabularyMatcher.matchProperties("srsc", "srsc", false, true),
VocabularyMatcher.matchProperties("common_types", "common_types", true, false),
VocabularyMatcher.matchProperties("common_iso_languages", "common_iso_languages", true , false),
VocabularyMatcher.matchProperties("SolrAuthorAuthority", "SolrAuthorAuthority", false , false)
)))
.andExpect(jsonPath("$._links.self.href",
Matchers.containsString("api/submission/vocabularies")))
.andExpect(jsonPath("$.page.totalElements", is(4)));
}
@Test
public void findOneSRSC_Test() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(get("/api/submission/vocabularies/srsc"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", is(
VocabularyMatcher.matchProperties("srsc", "srsc", false, true)
)));
}
@Test
public void findOneCommonTypesTest() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(get("/api/submission/vocabularies/common_types"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", is(
VocabularyMatcher.matchProperties("common_types", "common_types", true, false)
)));
}
@Test
public void correctSrscQueryTest() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(
get("/api/submission/vocabularies/srsc/entries")
.param("filter", "Research")
.param("size", "2"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.entries", Matchers.containsInAnyOrder(
VocabularyMatcher.matchVocabularyEntry("Research Subject Categories",
"Research Subject Categories", "vocabularyEntry"),
VocabularyMatcher.matchVocabularyEntry("Family research",
"Research Subject Categories::SOCIAL SCIENCES::Social sciences::Social work::Family research",
"vocabularyEntry"))))
.andExpect(jsonPath("$.page.totalElements", Matchers.is(26)))
.andExpect(jsonPath("$.page.totalPages", Matchers.is(13)))
.andExpect(jsonPath("$.page.size", Matchers.is(2)));
}
@Test
public void notScrollableVocabularyRequiredQueryTest() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(get("/api/submission/vocabularies/srsc/entries"))
.andExpect(status().isUnprocessableEntity());
}
@Test
public void noResultsSrscQueryTest() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(
get("/api/submission/vocabularies/srsc/entries")
.param("metadata", "dc.subject")
.param("filter", "Research2")
.param("size", "1000"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.page.totalElements", Matchers.is(0)));
}
@Test
public void vocabularyEntriesCommonTypesWithPaginationTest() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
getClient(token)
.perform(get("/api/submission/vocabularies/common_types/entries").param("size", "2"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.entries", Matchers.containsInAnyOrder(
VocabularyMatcher.matchVocabularyEntry("Animation", "Animation", "vocabularyEntry"),
VocabularyMatcher.matchVocabularyEntry("Article", "Article", "vocabularyEntry")
)))
.andExpect(jsonPath("$._embedded.entries[*].authority").doesNotExist())
.andExpect(jsonPath("$.page.totalElements", Matchers.is(22)))
.andExpect(jsonPath("$.page.totalPages", Matchers.is(11)))
.andExpect(jsonPath("$.page.size", Matchers.is(2)));
//second page
getClient(token).perform(get("/api/submission/vocabularies/common_types/entries")
.param("size", "2")
.param("page", "1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.entries", Matchers.containsInAnyOrder(
VocabularyMatcher.matchVocabularyEntry("Book", "Book", "vocabularyEntry"),
VocabularyMatcher.matchVocabularyEntry("Book chapter", "Book chapter", "vocabularyEntry")
)))
.andExpect(jsonPath("$._embedded.entries[*].authority").doesNotExist())
.andExpect(jsonPath("$.page.totalElements", Matchers.is(22)))
.andExpect(jsonPath("$.page.totalPages", Matchers.is(11)))
.andExpect(jsonPath("$.page.size", Matchers.is(2)));
}
@Test
public void vocabularyEntriesCommon_typesWithQueryTest() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(get("/api/submission/vocabularies/common_types/entries")
.param("filter", "Book")
.param("size", "2"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.entries", Matchers.containsInAnyOrder(
VocabularyMatcher.matchVocabularyEntry("Book", "Book", "vocabularyEntry"),
VocabularyMatcher.matchVocabularyEntry("Book chapter", "Book chapter", "vocabularyEntry")
)))
.andExpect(jsonPath("$.page.totalElements", Matchers.is(2)))
.andExpect(jsonPath("$.page.totalPages", Matchers.is(1)))
.andExpect(jsonPath("$.page.size", Matchers.is(2)));
}
@Test
public void vocabularyEntriesDCInputAuthorityLocalesTest() throws Exception {
String[] supportedLanguage = {"it","uk"};
configurationService.setProperty("default.locale","it");
configurationService.setProperty("webui.supported.locales",supportedLanguage);
// These clears have to happen so that the config is actually reloaded in those classes. This is needed for
// the properties that we're altering above and this is only used within the tests
submissionFormRestRepository.reload();
DCInputAuthority.reset();
pluginService.clearNamedPluginClasses();
cas.clearCache();
Locale uk = new Locale("uk");
Locale it = new Locale("it");
String token = getAuthToken(admin.getEmail(), password);
getClient(token)
.perform(get("/api/submission/vocabularies/common_iso_languages/entries")
.param("size", "2")
.locale(it))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.entries", Matchers.containsInAnyOrder(
VocabularyMatcher.matchVocabularyEntry("N/A", "", "vocabularyEntry"),
VocabularyMatcher.matchVocabularyEntry("Inglese (USA)", "en_US", "vocabularyEntry")
)))
.andExpect(jsonPath("$._embedded.entries[*].authority").doesNotExist())
.andExpect(jsonPath("$.page.totalElements", Matchers.is(12)))
.andExpect(jsonPath("$.page.totalPages", Matchers.is(6)))
.andExpect(jsonPath("$.page.size", Matchers.is(2)));
getClient(token)
.perform(get("/api/submission/vocabularies/common_iso_languages/entries")
.param("size", "2")
.locale(uk))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.entries", Matchers.containsInAnyOrder(
VocabularyMatcher.matchVocabularyEntry("N/A", "", "vocabularyEntry"),
VocabularyMatcher.matchVocabularyEntry("Американська (USA)", "en_US", "vocabularyEntry")
)))
.andExpect(jsonPath("$._embedded.entries[*].authority").doesNotExist())
.andExpect(jsonPath("$.page.totalElements", Matchers.is(12)))
.andExpect(jsonPath("$.page.totalPages", Matchers.is(6)))
.andExpect(jsonPath("$.page.size", Matchers.is(2)));
configurationService.setProperty("default.locale","en");
configurationService.setProperty("webui.supported.locales",null);
submissionFormRestRepository.reload();
DCInputAuthority.reset();
pluginService.clearNamedPluginClasses();
cas.clearCache();
}
@Test
public void correctSolrQueryTest() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(
get("/api/submission/vocabularies/SolrAuthorAuthority/entries")
.param("filter", "Shirasaka")
.param("size", "1000"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.entries", Matchers.contains(
VocabularyMatcher.matchVocabularyEntry("Shirasaka, Seiko", "Shirasaka, Seiko", "vocabularyEntry")
)))
.andExpect(jsonPath("$._embedded.entries[0].authority").isNotEmpty())
.andExpect(jsonPath("$.page.totalElements", Matchers.is(1)));
}
@Test
public void noResultsSolrQueryTest() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(
get("/api/submission/vocabularies/SolrAuthorAuthority/entries")
.param("filter", "Smith")
.param("size", "1000"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.page.totalElements", Matchers.is(0)));
}
@Test
public void findByMetadataAndCollectionTest() throws Exception {
context.turnOffAuthorisationSystem();
Collection collection = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Test collection")
.build();
context.restoreAuthSystemState();
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(get("/api/submission/vocabularies/search/byMetadataAndCollection")
.param("metadata", "dc.type")
.param("collection", collection.getID().toString()))
.andExpect(status().isOk())
.andExpect(jsonPath("$", is(
VocabularyMatcher.matchProperties("common_types", "common_types", true, false)
)));
}
@Test
public void findByMetadataAndCollectionUnprocessableEntityTest() throws Exception {
context.turnOffAuthorisationSystem();
Collection collection = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Test collection")
.build();
context.restoreAuthSystemState();
String token = getAuthToken(admin.getEmail(), password);
getClient(token).perform(get("/api/submission/vocabularies/search/byMetadataAndCollection")
.param("metadata", "dc.not.exist")
.param("collection", collection.getID().toString()))
.andExpect(status().isUnprocessableEntity());
getClient(token).perform(get("/api/submission/vocabularies/search/byMetadataAndCollection")
.param("metadata", "dc.type")
.param("collection", UUID.randomUUID().toString()))
.andExpect(status().isUnprocessableEntity());
}
@Test
public void findByMetadataAndCollectionBadRequestTest() throws Exception {
context.turnOffAuthorisationSystem();
Collection collection = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Test collection")
.build();
context.restoreAuthSystemState();
String token = getAuthToken(admin.getEmail(), password);
//missing metadata
getClient(token).perform(get("/api/submission/vocabularies/search/byMetadataAndCollection")
.param("collection", collection.getID().toString()))
.andExpect(status().isBadRequest());
//missing collection
getClient(token).perform(get("/api/submission/vocabularies/search/byMetadataAndCollection")
.param("metadata", "dc.type"))
.andExpect(status().isBadRequest());
}
@Test
public void linkedEntitiesWithExactParamTest() throws Exception {
String token = getAuthToken(eperson.getEmail(), password);
getClient(token).perform(get("/api/submission/vocabularies/common_types/entries")
.param("filter", "Animation")
.param("exact", "true"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.entries", Matchers.contains(
VocabularyMatcher.matchVocabularyEntry("Animation", "Animation", "vocabularyEntry")
)))
.andExpect(jsonPath("$.page.totalElements", Matchers.is(1)));
}
@Test
public void linkedEntitiesWithFilterAndEntryIdTest() throws Exception {
String token = getAuthToken(eperson.getEmail(), password);
getClient(token).perform(get("/api/submission/vocabularies/srsc/entries")
.param("filter", "Research")
.param("entryID", "VR131402"))
.andExpect(status().isBadRequest());
}
}

View File

@@ -28,8 +28,8 @@ public class SubmissionFormFieldMatcher {
/** /**
* Shortcut for the * Shortcut for the
* {@link SubmissionFormFieldMatcher#matchFormFieldDefinition(String, String, String, boolean, String, String, String)} * {@link SubmissionFormFieldMatcher#matchFormFieldDefinition(String, String, String, boolean, String, String, String, String)}
* with a null style * with a null style and vocabulary name
* *
* @param type * @param type
* the expected input type * the expected input type
@@ -53,7 +53,9 @@ public class SubmissionFormFieldMatcher {
} }
/** /**
* Check the json representation of a submission form * Shortcut for the
* {@link SubmissionFormFieldMatcher#matchFormFieldDefinition(String, String, String, boolean, String, String, String, String)}
* with a null controlled vocabulary
* *
* @param type * @param type
* the expected input type * the expected input type
@@ -74,13 +76,45 @@ public class SubmissionFormFieldMatcher {
* @return a Matcher for all the condition above * @return a Matcher for all the condition above
*/ */
public static Matcher<? super Object> matchFormFieldDefinition(String type, String label, String mandatoryMessage, public static Matcher<? super Object> matchFormFieldDefinition(String type, String label, String mandatoryMessage,
boolean repeatable, boolean repeatable,
String hints, String style, String metadata) { String hints, String style, String metadata) {
return matchFormFieldDefinition(type, label, mandatoryMessage, repeatable, hints, style, metadata, null);
}
/**
* Check the json representation of a submission form
*
* @param type
* the expected input type
* @param label
* the expected label
* @param mandatoryMessage
* the expected mandatoryMessage, can be null. If not empty the field is expected to be flagged as
* mandatory
* @param repeatable
* the expected repeatable flag
* @param hints
* the expected hints message
* @param style
* the expected style for the field, can be null. If null the corresponding json path is expected to be
* missing
* @param metadata
* the expected metadata
* @param controlled vocabulary
* the expected controlled vocabulary, can be null. If null the corresponding json path is expected to be
* missing
* @return a Matcher for all the condition above
*/
public static Matcher<? super Object> matchFormFieldDefinition(String type, String label, String mandatoryMessage,
boolean repeatable, String hints, String style,
String metadata, String controlledVocabulary) {
return allOf( return allOf(
// check each field definition // check each field definition
hasJsonPath("$.input.type", is(type)), hasJsonPath("$.input.type", is(type)),
hasJsonPath("$.label", containsString(label)), hasJsonPath("$.label", containsString(label)),
hasJsonPath("$.selectableMetadata[0].metadata", is(metadata)), hasJsonPath("$.selectableMetadata[0].metadata", is(metadata)),
controlledVocabulary != null ? hasJsonPath("$.selectableMetadata[0].controlledVocabulary",
is(controlledVocabulary)) : hasNoJsonPath("$.selectableMetadata[0].controlledVocabulary"),
mandatoryMessage != null ? hasJsonPath("$.mandatoryMessage", containsString(mandatoryMessage)) : mandatoryMessage != null ? hasJsonPath("$.mandatoryMessage", containsString(mandatoryMessage)) :
hasNoJsonPath("$.mandatoryMessage"), hasNoJsonPath("$.mandatoryMessage"),
hasJsonPath("$.mandatory", is(mandatoryMessage != null)), hasJsonPath("$.mandatory", is(mandatoryMessage != null)),

View File

@@ -8,7 +8,6 @@
package org.dspace.app.rest.matcher; package org.dspace.app.rest.matcher;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static org.dspace.app.rest.matcher.HalMatcher.matchEmbeds;
import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
@@ -18,20 +17,20 @@ import org.hamcrest.Matcher;
/** /**
* This matcher has been created so that we can use a predefined Matcher class to verify Authority Entries * This matcher has been created so that we can use a predefined Matcher class to verify Authority Entries
*/ */
public class AuthorityEntryMatcher { public class VocabularyEntryDetailsMatcher {
private AuthorityEntryMatcher() { private VocabularyEntryDetailsMatcher() {
} }
public static Matcher<? super Object> matchAuthorityEntry(String id, String display, String value) { public static Matcher<? super Object> matchAuthorityEntry(String id, String display, String value) {
return allOf( return allOf(
matchProperties(id, display, value), matchProperties(id, display, value),
matchLinks()); matchLinks(id));
} }
public static Matcher<? super Object> matchLinks() { public static Matcher<? super Object> matchLinks(String id) {
return allOf( return allOf(
hasJsonPath("$._links.self.href", containsString("api/integration/authority/"))); hasJsonPath("$._links.self.href", containsString("api/submission/vocabularyEntryDetails/" + id)));
} }
private static Matcher<? super Object> matchProperties(String id, String display, String value) { private static Matcher<? super Object> matchProperties(String id, String display, String value) {
@@ -39,16 +38,7 @@ public class AuthorityEntryMatcher {
hasJsonPath("$.id", is(id)), hasJsonPath("$.id", is(id)),
hasJsonPath("$.display", is(display)), hasJsonPath("$.display", is(display)),
hasJsonPath("$.value", is(value)), hasJsonPath("$.value", is(value)),
hasJsonPath("$.type", is("authority")) hasJsonPath("$.type", is("vocabularyEntryDetail"))
);
}
/**
* Gets a matcher for all expected embeds when the full projection is requested.
*/
public static Matcher<? super Object> matchFullEmbeds() {
return matchEmbeds(
"authorityEntries"
); );
} }
} }

View File

@@ -0,0 +1,44 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.matcher;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.is;
import org.hamcrest.Matcher;
/**
*
*
* @author mykhaylo
*
*/
public class VocabularyMatcher {
private VocabularyMatcher() {}
public static Matcher<? super Object> matchProperties(String id, String name,
boolean scrollable, boolean hierarchical) {
return allOf(
hasJsonPath("$.id", is(id)),
hasJsonPath("$.name", is(name)),
hasJsonPath("$.scrollable", is(scrollable)),
hasJsonPath("$.hierarchical", is(hierarchical)),
hasJsonPath("$.type", is("vocabulary"))
);
}
public static Matcher<? super Object> matchVocabularyEntry(String display, String value, String type) {
return allOf(
hasJsonPath("$.display", is(display)),
hasJsonPath("$.value", is(value)),
hasJsonPath("$.type", is(type))
);
}
}

View File

@@ -58,6 +58,7 @@ or refer to the Web site http://dspace-dev.dsi.uminho.pt.
</xs:all> </xs:all>
<xs:attribute name="id" type="xs:ID" use="optional"/> <xs:attribute name="id" type="xs:ID" use="optional"/>
<xs:attribute name="label" type="xs:string" use="required"/> <xs:attribute name="label" type="xs:string" use="required"/>
<xs:attribute name="selectable" type="xs:boolean" default="true"/>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
</xs:schema> </xs:schema>

View File

@@ -1486,7 +1486,7 @@ orcid.url = https://orcid.org/
## eg: nsi, srsc. ## eg: nsi, srsc.
## Each DSpaceControlledVocabulary plugin comes with three configuration options: ## Each DSpaceControlledVocabulary plugin comes with three configuration options:
# vocabulary.plugin._plugin_.hierarchy.store = <true|false> # default: true # vocabulary.plugin._plugin_.hierarchy.store = <true|false> # default: true
# vocabulary.plugin._plugin_.hierarchy.suggest = <true|false> # default: true # vocabulary.plugin._plugin_.hierarchy.suggest = <true|false> # default: false
# vocabulary.plugin._plugin_.delimiter = "<string>" # default: "::" # vocabulary.plugin._plugin_.delimiter = "<string>" # default: "::"
## ##
## An example using "srsc" can be found later in this section ## An example using "srsc" can be found later in this section