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;
/**
* 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 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) {
this.authority = authority;
this.value = value;
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) {
this.authority = authority;
this.label = label;
this.value = value;
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;
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
@@ -17,7 +20,7 @@ import org.dspace.content.Collection;
* @see ChoiceAuthorityServiceImpl
* @see MetadataAuthorityServiceImpl
*/
public interface ChoiceAuthority {
public interface ChoiceAuthority extends NameAwarePlugin {
/**
* Get all values from the authority that match the preferred value.
* 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,
* that matches the value.
*
* @param field being matched for
* @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 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 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
@@ -51,13 +52,11 @@ public interface ChoiceAuthority {
* This call is typically used in non-interactive metadata ingest
* where there is no interactive agent to choose from among options.
*
* @param field being matched for
* @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
* @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)
@@ -67,31 +66,97 @@ public interface ChoiceAuthority {
* This may get called many times while populating a Web page so it should
* be implemented as efficiently as possible.
*
* @param field being matched for
* @param key authority key known to this authority.
* @param locale explicit localization key if available, or 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() {
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() {
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();
result.authority = authKey;
result.label = getLabel(fieldKey, authKey, locale);
result.value = getLabel(fieldKey, authKey, locale);
result.label = getLabel(authKey, locale);
result.value = getValue(authKey, locale);
result.extras.putAll(getExtra(authKey, locale));
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;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
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.DCInputsReader;
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.MetadataValue;
import org.dspace.content.authority.service.ChoiceAuthorityService;
@@ -54,23 +60,37 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
// map of field key to authority plugin
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
protected Map<String, String> presentation = new HashMap<String, String>();
// map of field key to closed value
protected Map<String, Boolean> closed = new HashMap<String, Boolean>();
// map of authority name to field key
protected Map<String, String> authorities = new HashMap<String, String>();
// flag to track the initialization status of the service
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)
protected ConfigurationService configurationService;
@Autowired(required = true)
protected PluginService pluginService;
private final String CHOICES_PLUGIN_PREFIX = "choices.plugin.";
private final String CHOICES_PRESENTATION_PREFIX = "choices.presentation.";
private final String CHOICES_CLOSED_PREFIX = "choices.closed.";
final static String CHOICES_PLUGIN_PREFIX = "choices.plugin.";
final static String CHOICES_PRESENTATION_PREFIX = "choices.presentation.";
final static String CHOICES_CLOSED_PREFIX = "choices.closed.";
protected ChoiceAuthorityServiceImpl() {
}
@@ -96,10 +116,25 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
@Override
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();
initialized = true;
}
return authorities.keySet();
}
@Override
@@ -112,59 +147,62 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
@Override
public Choices getMatches(String fieldKey, String query, Collection collection,
int start, int limit, String locale) {
ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey);
ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection);
if (ma == null) {
throw new IllegalArgumentException(
"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
public Choices getMatches(String fieldKey, String query, Collection collection, int start, int limit, String locale,
boolean externalInput) {
ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey);
ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection);
if (ma == null) {
throw new IllegalArgumentException(
"No choices plugin was configured for field \"" + fieldKey
+ "\".");
+ "\", collection=" + collection.getID().toString() + ".");
}
if (externalInput && ma instanceof SolrAuthority) {
((SolrAuthority) ma).addExternalResultsInNextMatches();
}
return ma.getMatches(fieldKey, query, collection, start, limit, locale);
return ma.getMatches(query, start, limit, locale);
}
@Override
public Choices getBestMatch(String fieldKey, String query, Collection collection,
String locale) {
ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey);
ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection);
if (ma == null) {
throw new IllegalArgumentException(
"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
public String getLabel(MetadataValue metadataValue, String locale) {
return getLabel(metadataValue.getMetadataField().toString(), metadataValue.getAuthority(), locale);
public String getLabel(MetadataValue metadataValue, Collection collection, String locale) {
return getLabel(metadataValue.getMetadataField().toString(), collection, metadataValue.getAuthority(), locale);
}
@Override
public String getLabel(String fieldKey, String authKey, String locale) {
ChoiceAuthority ma = getChoiceAuthorityMap().get(fieldKey);
public String getLabel(String fieldKey, Collection collection, String authKey, String locale) {
ChoiceAuthority ma = getAuthorityByFieldKeyCollection(fieldKey, collection);
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
public boolean isChoicesConfigured(String fieldKey) {
return getChoiceAuthorityMap().containsKey(fieldKey);
public boolean isChoicesConfigured(String fieldKey, Collection collection) {
return getAuthorityByFieldKeyCollection(fieldKey, collection) != null;
}
@Override
@@ -178,8 +216,14 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
}
@Override
public List<String> getVariants(MetadataValue metadataValue) {
ChoiceAuthority ma = getChoiceAuthorityMap().get(metadataValue.getMetadataField().toString());
public List<String> getVariants(MetadataValue metadataValue, Collection collection) {
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) {
AuthorityVariantsSupport avs = (AuthorityVariantsSupport) ma;
return avs.getVariants(metadataValue.getAuthority(), metadataValue.getLanguage());
@@ -189,42 +233,53 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
@Override
public String getChoiceAuthorityName(String schema, String element, String qualifier) {
String makeFieldKey = makeFieldKey(schema, element, qualifier);
if (getChoiceAuthorityMap().containsKey(makeFieldKey)) {
for (String key : this.authorities.keySet()) {
if (this.authorities.get(key).equals(makeFieldKey)) {
return key;
public String getChoiceAuthorityName(String schema, String element, String qualifier, Collection collection) {
init();
String fieldKey = makeFieldKey(schema, element, qualifier);
// check if there is an authority configured for the metadata valid for all the collections
if (controller.containsKey(fieldKey)) {
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(
CHOICES_PLUGIN_PREFIX + schema + "." + element + (qualifier != null ? "." + qualifier : ""));
return null;
}
protected String makeFieldKey(String schema, String element, String 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
public void clearCache() {
controller.clear();
authorities.clear();
presentation.clear();
closed.clear();
controllerFormDefinitions.clear();
authoritiesFormDefinitions.clear();
itemSubmissionConfigReader = null;
initialized = false;
}
private void loadChoiceAuthorityConfigurations() {
// Get all configuration keys starting with a given 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);
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);
}
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() {
try {
List<SubmissionConfig> submissionConfigs = itemSubmissionConfigReader
.getAllSubmissionConfigs(Integer.MAX_VALUE, 0);
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)) {
// loop over all the defined item submission configuration
for (SubmissionConfig subCfg : submissionConfigs) {
String submissionName = subCfg.getSubmissionName();
List<DCInputSet> inputsBySubmissionName = dcInputsReader.getInputsBySubmissionName(submissionName);
// loop over the submission forms configuration eventually associated with the submission panel
for (DCInputSet dcinputSet : inputsBySubmissionName) {
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();
}
if (!StringUtils.equals(dcinput.getInputType(), "qualdrop_value")) {
// do we have an authority?
if (StringUtils.isNotBlank(authorityName)) {
String fieldKey = makeFieldKey(dcinput.getSchema(), dcinput.getElement(),
dcinput.getQualifier());
ChoiceAuthority ca = controller.get(authorityName);
if (ca == null) {
InputFormSelfRegisterWrapperAuthority ifa = new
InputFormSelfRegisterWrapperAuthority();
if (controller.containsKey(fieldKey)) {
ifa = (InputFormSelfRegisterWrapperAuthority) controller.get(fieldKey);
}
ChoiceAuthority ma = (ChoiceAuthority) pluginService
ca = (ChoiceAuthority) pluginService
.getNamedPlugin(ChoiceAuthority.class, authorityName);
if (ma == null) {
log.warn("Skipping invalid configuration for " + fieldKey
+ " because named plugin not found: " + authorityName);
continue;
if (ca == null) {
throw new IllegalStateException("Invalid configuration for " + fieldKey
+ " in submission definition " + submissionName
+ ", 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) {
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
*
@@ -370,26 +481,6 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
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
public ChoiceAuthority getChoiceAuthorityByAuthorityName(String authorityName) {
ChoiceAuthority ma = (ChoiceAuthority)
@@ -401,4 +492,68 @@ public final class ChoiceAuthorityServiceImpl implements ChoiceAuthorityService
}
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.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
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.StringUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.app.util.DCInputsReader;
import org.dspace.app.util.DCInputsReaderException;
import org.dspace.content.Collection;
import org.dspace.core.I18nUtil;
import org.dspace.core.SelfNamedPlugin;
/**
@@ -44,16 +50,38 @@ import org.dspace.core.SelfNamedPlugin;
public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority {
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;
public DCInputAuthority() {
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() {
if (pluginNames == null) {
initPluginNames();
@@ -63,20 +91,28 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority
}
private static synchronized void initPluginNames() {
Locale[] locales = I18nUtil.getSupportedLocales();
Set<String> names = new HashSet<String>();
if (pluginNames == null) {
try {
if (dci == null) {
dci = new DCInputsReader();
dcis = new HashMap<Locale, 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) {
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()]);
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
private void init() {
if (values == null) {
values = new HashMap<String, String[]>();
labels = new HashMap<String, String[]>();
String pname = this.getPluginInstanceName();
List<String> pairs = dci.getPairs(pname);
if (pairs != null) {
values = new String[pairs.size() / 2];
labels = new String[pairs.size() / 2];
for (int i = 0; i < pairs.size(); i += 2) {
labels[i / 2] = pairs.get(i);
values[i / 2] = pairs.get(i + 1);
for (Locale l : dcis.keySet()) {
DCInputsReader dci = dcis.get(l);
List<String> pairs = dci.getPairs(pname);
if (pairs != null) {
String[] valuesLocale = new String[pairs.size() / 2];
String[]labelsLocale = new String[pairs.size() / 2];
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
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();
Locale currentLocale = I18nUtil.getSupportedLocale(locale);
String[] valuesLocale = values.get(currentLocale.getLanguage());
String[] labelsLocale = labels.get(currentLocale.getLanguage());
int dflt = -1;
Choice v[] = new Choice[values.length];
for (int i = 0; i < values.length; ++i) {
v[i] = new Choice(values[i], values[i], labels[i]);
if (values[i].equalsIgnoreCase(query)) {
dflt = i;
int found = 0;
List<Choice> v = new ArrayList<Choice>();
for (int i = 0; i < valuesLocale.length; ++i) {
if (query == null || StringUtils.containsIgnoreCase(valuesLocale[i], query)) {
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
public Choices getBestMatch(String field, String text, Collection collection, String locale) {
public Choices getBestMatch(String text, String locale) {
init();
for (int i = 0; i < values.length; ++i) {
if (text.equalsIgnoreCase(values[i])) {
Locale currentLocale = I18nUtil.getSupportedLocale(locale);
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];
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);
}
}
@@ -131,19 +187,31 @@ public class DCInputAuthority extends SelfNamedPlugin implements ChoiceAuthority
}
@Override
public String getLabel(String field, String key, String locale) {
public String getLabel(String key, String locale) {
init();
// Get default if locale is empty
if (StringUtils.isBlank(locale)) {
locale = I18nUtil.getDefaultLocale().getLanguage();
}
String[] labelsLocale = labels.get(locale);
int pos = -1;
for (int i = 0; i < values.length; i++) {
if (values[i].equals(key)) {
for (int i = 0; i < labelsLocale.length; i++) {
if (labelsLocale[i].equals(key)) {
pos = i;
break;
}
}
if (pos != -1) {
return labels[pos];
return labelsLocale[pos];
} else {
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.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
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.StringUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.content.Collection;
import org.dspace.core.SelfNamedPlugin;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
@@ -54,25 +55,35 @@ import org.xml.sax.InputSource;
* @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);
protected static String xpathTemplate = "//node[contains(translate(@label,'ABCDEFGHIJKLMNOPQRSTUVWXYZ'," +
"'abcdefghijklmnopqrstuvwxyz'),'%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 String vocabularyName = null;
protected InputSource vocabulary = null;
protected Boolean suggestHierarchy = true;
protected Boolean suggestHierarchy = false;
protected Boolean storeHierarchy = true;
protected String hierarchyDelimiter = "::";
protected Integer preloadLevel = 1;
public DSpaceControlledVocabulary() {
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() {
if (pluginNames == null) {
initPluginNames();
@@ -112,6 +123,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic
String configurationPrefix = "vocabulary.plugin." + vocabularyName;
storeHierarchy = config.getBooleanProperty(configurationPrefix + ".hierarchy.store", storeHierarchy);
suggestHierarchy = config.getBooleanProperty(configurationPrefix + ".hierarchy.suggest", suggestHierarchy);
preloadLevel = config.getIntProperty(configurationPrefix + ".hierarchy.preloadLevel", preloadLevel);
String configuredDelimiter = config.getProperty(configurationPrefix + ".delimiter");
if (configuredDelimiter != null) {
hierarchyDelimiter = configuredDelimiter.replaceAll("(^\"|\"$)", "");
@@ -142,7 +154,7 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic
}
@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();
log.debug("Getting matches for '" + text + "'");
String xpathExpression = "";
@@ -151,59 +163,60 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic
xpathExpression += String.format(xpathTemplate, textHierarchy[i].replaceAll("'", "&apos;").toLowerCase());
}
XPath xpath = XPathFactory.newInstance().newXPath();
Choice[] choices;
int total = 0;
List<Choice> choices = new ArrayList<Choice>();
try {
NodeList results = (NodeList) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET);
String[] authorities = new String[results.getLength()];
String[] values = new String[results.getLength()];
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]);
}
}
}
total = results.getLength();
choices = getChoicesFromNodeList(results, start, limit);
} 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
public Choices getBestMatch(String field, String text, Collection collection, String locale) {
public Choices getBestMatch(String text, String locale) {
init();
log.debug("Getting best match for '" + text + "'");
return getMatches(field, text, collection, 0, 2, locale);
}
@Override
public String getLabel(String field, String key, String locale) {
init();
String xpathExpression = String.format(idTemplate, key);
log.debug("Getting best matches for '" + text + "'");
String xpathExpression = "";
String[] textHierarchy = text.split(hierarchyDelimiter, -1);
for (int i = 0; i < textHierarchy.length; i++) {
xpathExpression += String.format(labelTemplate, textHierarchy[i].replaceAll("'", "&apos;"));
}
XPath xpath = XPathFactory.newInstance().newXPath();
List<Choice> choices = new ArrayList<Choice>();
try {
Node node = (Node) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODE);
return node.getAttributes().getNamedItem("label").getNodeValue();
NodeList results = (NodeList) xpath.evaluate(xpathExpression, vocabulary, XPathConstants.NODESET);
choices = getChoicesFromNodeList(results, 0, 1);
} 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
@@ -212,81 +225,227 @@ public class DSpaceControlledVocabulary extends SelfNamedPlugin implements Choic
}
@Override
public Choice getChoice(String fieldKey, String authKey, String locale) {
public Choices getTopChoices(String authorityName, int start, int limit, String locale) {
init();
log.debug("Getting matches for '" + authKey + "'");
String xpathExpression = String.format(idTemplate, authKey);
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;
String xpathExpression = rootTemplate;
return getChoicesByXpath(xpathExpression, start, limit);
}
private void readNode(String[] authorities, String[] values, String[] labels, String[] parent, String[] notes,
int i, Node node) {
@Override
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);
if (this.suggestHierarchy) {
labels[i] = hierarchy;
return hierarchy;
} else {
labels[i] = node.getAttributes().getNamedItem("label").getNodeValue();
}
if (this.storeHierarchy) {
values[i] = hierarchy;
} else {
values[i] = node.getAttributes().getNamedItem("label").getNodeValue();
return 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();
for (int ci = 0; ci < childNodes.getLength(); ci++) {
Node firstChild = childNodes.item(ci);
if (firstChild != null && "hasNote".equals(firstChild.getNodeName())) {
String nodeValue = firstChild.getTextContent();
if (StringUtils.isNotBlank(nodeValue)) {
notes[i] = nodeValue;
return nodeValue;
}
}
}
Node idAttr = node.getAttributes().getNamedItem("id");
if (null != idAttr) { // 'id' is optional
authorities[i] = idAttr.getNodeValue();
if (isHierarchical()) {
Node parentN = node.getParentNode();
if (parentN != null) {
parentN = parentN.getParentNode();
if (parentN != null) {
Node parentIdAttr = parentN.getAttributes().getNamedItem("id");
if (null != parentIdAttr) {
parent[i] = parentIdAttr.getNodeValue();
return null;
}
private List<String> getChildren(Node node) {
List<String> children = new ArrayList<String>();
NodeList childNodes = node.getChildNodes();
for (int ci = 0; ci < childNodes.getLength(); ci++) {
Node firstChild = childNodes.item(ci);
if (firstChild != null && "isComposedBy".equals(firstChild.getNodeName())) {
for (int cii = 0; cii < firstChild.getChildNodes().getLength(); cii++) {
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;
parent[i] = null;
}
return children;
}
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.Map;
import org.apache.commons.lang3.StringUtils;
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.authority.service.MetadataAuthorityService;
import org.dspace.content.service.MetadataFieldService;
@@ -144,8 +139,6 @@ public class MetadataAuthorityServiceImpl implements MetadataAuthorityService {
if (dmc >= Choices.CF_UNSET) {
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
* for the given metadata.
@@ -229,35 +221,4 @@ public class MetadataAuthorityServiceImpl implements MetadataAuthorityService {
}
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;
import org.dspace.content.Collection;
/**
* This is a *very* stupid test fixture for authority control, and also
* serves as a trivial example of an authority plugin implementation.
*/
public class SampleAuthority implements ChoiceAuthority {
private String pluginInstanceName;
protected static String values[] = {
"sun",
"mon",
@@ -35,7 +35,7 @@ public class SampleAuthority implements ChoiceAuthority {
};
@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;
Choice v[] = new Choice[values.length];
for (int i = 0; i < values.length; ++i) {
@@ -48,7 +48,7 @@ public class SampleAuthority implements ChoiceAuthority {
}
@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) {
if (text.equalsIgnoreCase(values[i])) {
Choice v[] = new Choice[1];
@@ -60,7 +60,17 @@ public class SampleAuthority implements ChoiceAuthority {
}
@Override
public String getLabel(String field, String key, String locale) {
public String getLabel(String key, String locale) {
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.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
@@ -24,8 +25,9 @@ import org.dspace.authority.AuthorityValue;
import org.dspace.authority.SolrAuthorityInterface;
import org.dspace.authority.factory.AuthorityServiceFactory;
import org.dspace.authority.service.AuthorityValueService;
import org.dspace.content.Collection;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.NameAwarePlugin;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
/**
@@ -35,7 +37,14 @@ import org.dspace.services.factory.DSpaceServicesFactory;
* @author Mark Diggory (markd at atmire dot com)
*/
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 =
DSpaceServicesFactory.getInstance().getServiceManager()
.getServiceByName("AuthoritySource", SolrAuthorityInterface.class);
@@ -45,8 +54,9 @@ public class SolrAuthority implements ChoiceAuthority {
protected boolean externalResults = false;
protected final AuthorityValueService authorityValueService = AuthorityServiceFactory.getInstance()
.getAuthorityValueService();
public Choices getMatches(String field, String text, Collection collection, int start, int limit, String locale,
protected final ConfigurationService configurationService = DSpaceServicesFactory.getInstance()
.getConfigurationService();
public Choices getMatches(String text, int start, int limit, String locale,
boolean bestMatch) {
if (limit == 0) {
limit = 10;
@@ -193,13 +203,13 @@ public class SolrAuthority implements ChoiceAuthority {
}
@Override
public Choices getMatches(String field, String text, Collection collection, int start, int limit, String locale) {
return getMatches(field, text, collection, start, limit, locale, true);
public Choices getMatches(String text, int start, int limit, String locale) {
return getMatches(text, start, limit, locale, true);
}
@Override
public Choices getBestMatch(String field, String text, Collection collection, String locale) {
Choices matches = getMatches(field, text, collection, 0, 1, locale, false);
public Choices getBestMatch(String text, String locale) {
Choices matches = getMatches(text, 0, 1, locale, false);
if (matches.values.length != 0 && !matches.values[0].value.equalsIgnoreCase(text)) {
matches = new Choices(false);
}
@@ -207,7 +217,7 @@ public class SolrAuthority implements ChoiceAuthority {
}
@Override
public String getLabel(String field, String key, String locale) {
public String getLabel(String key, String locale) {
try {
if (log.isDebugEnabled()) {
log.debug("requesting label for key " + key + " using locale " + locale);
@@ -276,4 +286,23 @@ public class SolrAuthority implements ChoiceAuthority {
public void addExternalResultsInNextMatches() {
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 org.apache.commons.lang3.StringUtils;
import org.dspace.content.Collection;
/**
* 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)
*/
public class TestAuthority implements ChoiceAuthority, AuthorityVariantsSupport {
private String pluginInstanceName;
@Override
public List<String> getVariants(String key, String locale) {
@@ -33,8 +33,7 @@ public class TestAuthority implements ChoiceAuthority, AuthorityVariantsSupport
}
@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) {
Choices choices = new Choices(false);
if (StringUtils.isNotBlank(text)) {
@@ -52,8 +51,7 @@ public class TestAuthority implements ChoiceAuthority, AuthorityVariantsSupport
}
@Override
public Choices getBestMatch(String field, String text, Collection collection,
String locale) {
public Choices getBestMatch(String text, String locale) {
Choices choices = new Choices(false);
if (StringUtils.isNotBlank(text)) {
@@ -70,10 +68,20 @@ public class TestAuthority implements ChoiceAuthority, AuthorityVariantsSupport
}
@Override
public String getLabel(String field, String key, String locale) {
public String getLabel(String key, String locale) {
if (StringUtils.isNotBlank(key)) {
return key.replaceAll("authority", "label");
}
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 qualifier qualifier of metadata field
* @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
*/
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
@@ -112,30 +112,33 @@ public interface ChoiceAuthorityService {
* the metadata field defined by schema,element,qualifier.
*
* @param metadataValue metadata value
* @param collection Collection owner of Item
* @param locale explicit localization key if available
* @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
* the metadata field defined by single field key.
*
* @param fieldKey single string identifying metadata field
* @param collection Collection owner of Item
* @param locale explicit localization key if available
* @param authKey authority key
* @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
* given metadata field?
*
* @param fieldKey single string identifying metadata field
* @param collection Collection owner of Item
* @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
@@ -160,12 +163,14 @@ public interface ChoiceAuthorityService {
* @param metadataValue metadata value
* @return List of variants
*/
public List<String> getVariants(MetadataValue metadataValue);
public String getChoiceMetadatabyAuthorityName(String name);
public Choice getChoice(String fieldKey, String authKey, String locale);
public List<String> getVariants(MetadataValue metadataValue, Collection collection);
/**
* 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);
/**
@@ -173,4 +178,49 @@ public interface ChoiceAuthorityService {
*/
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;
}
/**
* 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

View File

@@ -345,8 +345,8 @@ public class LegacyPluginServiceImpl implements PluginService {
" for interface=" + iname +
" pluginName=" + name);
Object result = pluginClass.newInstance();
if (result instanceof SelfNamedPlugin) {
((SelfNamedPlugin) result).setPluginInstanceName(name);
if (result instanceof NameAwarePlugin) {
((NameAwarePlugin) result).setPluginInstanceName(name);
}
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$
* @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.
private String myName = null;
@@ -52,30 +52,13 @@ public abstract class SelfNamedPlugin {
return null;
}
/**
* 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.
*/
@Override
public String getPluginInstanceName() {
return myName;
}
/**
* 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.
*/
protected void setPluginInstanceName(String name) {
@Override
public void setPluginInstanceName(String name) {
myName = name;
}
}

View File

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

View File

@@ -173,6 +173,8 @@ public class ItemIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl<Indexable
public void addDiscoveryFields(SolrInputDocument doc, Context context, Item item,
List<DiscoveryConfiguration> discoveryConfigurations)
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
List<String> sortFieldsAdded = new ArrayList<>();
Map<String, List<DiscoverySearchFilter>> searchFilters = null;
@@ -359,7 +361,7 @@ public class ItemIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl<Indexable
if (!ignorePrefered) {
preferedLabel = choiceAuthorityService
.getLabel(meta, meta.getLanguage());
.getLabel(meta, collection, meta.getLanguage());
}
boolean ignoreVariants =
@@ -375,7 +377,7 @@ public class ItemIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl<Indexable
true);
if (!ignoreVariants) {
variants = choiceAuthorityService
.getVariants(meta);
.getVariants(meta, collection);
}
}

View File

@@ -109,6 +109,11 @@ plugin.sequence.java.util.Collection = \
java.util.Stack, \
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 #
# PROPERTY EXPOSURE VIA REST #

View File

@@ -282,6 +282,70 @@ it, please enter the types and the actual numbers or codes.</hint>
</row>
</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>

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
String dcType = "DC-TYPE";
String testType = "TEST-TYPE";
itemService.addMetadata(context, it, "dc", "type", null, null, dcType, "accepted", 0);
itemService.addMetadata(context, it, "test", "type", null, null, testType, "accepted", 0);
itemService.addMetadata(context, it, "dc", "type", null, null, dcType);
itemService.addMetadata(context, it, "test", "type", null, null, testType);
// Check that only one is returned when we ask for all dc.type values
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";
Collection collection = null;
int start = 0;
int limit = 0;
int limit = 10;
String locale = null;
// This "farm" Controlled Vocab is included in TestEnvironment data
// (under /src/test/data/dspaceFolder/) and it should be auto-loaded
@@ -86,8 +86,7 @@ public class DSpaceControlledVocabularyTest extends AbstractDSpaceTest {
DSpaceControlledVocabulary instance = (DSpaceControlledVocabulary)
CoreServiceFactory.getInstance().getPluginService().getNamedPlugin(Class.forName(PLUGIN_INTERFACE), "farm");
assertNotNull(instance);
Choices result = instance.getMatches(field, text, collection, start,
limit, locale);
Choices result = instance.getMatches(text, start, limit, locale);
assertEquals("the farm::north 40", result.values[0].value);
}

View File

@@ -149,7 +149,7 @@ public class RestResourceController implements InitializingBean {
* @return single DSpaceResource
*/
@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) {
return findOneInternal(apiCategory, model, id);
}
@@ -180,7 +180,7 @@ public class RestResourceController implements InitializingBean {
* @return single DSpaceResource
*/
@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) {
return findOneInternal(apiCategory, model, id);
}
@@ -200,7 +200,7 @@ public class RestResourceController implements InitializingBean {
* @return single DSpaceResource
*/
@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) {
return findOneInternal(apiCategory, model, uuid);
}
@@ -213,7 +213,7 @@ public class RestResourceController implements InitializingBean {
* @param id Identifier from request
* @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) {
DSpaceRestRepository<RestAddressableModel, ID> repository = utils.getResourceRepository(apiCategory, model);
Optional<RestAddressableModel> modelObject = Optional.empty();
@@ -799,7 +799,7 @@ public class RestResourceController implements InitializingBean {
Method linkMethod = utils.requireMethod(linkRepository.getClass(), linkRest.method());
try {
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());
if (pageResult == null) {
@@ -823,8 +823,8 @@ public class RestResourceController implements InitializingBean {
return new EntityModel(new EmbeddedPage(link.getHref(),
pageResult.map(converter::toResource), null, subpath));
} else {
RestModel object = (RestModel) linkMethod.invoke(linkRepository, request, uuid, page,
utils.obtainProjection());
RestModel object = (RestModel) linkMethod.invoke(linkRepository, request,
uuid, page, utils.obtainProjection());
if (object == null) {
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
return null;
@@ -846,7 +846,7 @@ public class RestResourceController implements InitializingBean {
throw new RuntimeException(e);
}
}
RestAddressableModel modelObject = repository.findById(uuid).orElse(null);
RestModel modelObject = repository.findById(uuid).orElse(null);
if (modelObject == null) {
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();
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(),
dcinput.getQualifier(), inputType));
selMd.setAuthority(getAuthorityName(dcinput.getSchema(), dcinput.getElement(),
selMd.setControlledVocabulary(getAuthorityName(dcinput.getSchema(), dcinput.getElement(),
dcinput.getQualifier(), dcinput.getPairsType(),
dcinput.getVocabulary()));
selMd.setClosed(
authorityUtils.isClosed(dcinput.getSchema(), dcinput.getElement(), dcinput.getQualifier()));
isClosed(dcinput.getSchema(), dcinput.getElement(), dcinput.getQualifier(),
dcinput.getPairsType(), dcinput.getVocabulary()));
} else {
inputRest.setType(inputType);
}
@@ -139,10 +141,10 @@ public class SubmissionFormConverter implements DSpaceConverter<DCInputSet, Subm
selMd.setMetadata(org.dspace.core.Utils
.standardize(dcinput.getSchema(), dcinput.getElement(), pairs.get(idx + 1), "."));
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()));
selMd.setClosed(authorityUtils.isClosed(dcinput.getSchema(), dcinput.getElement(),
dcinput.getQualifier()));
selMd.setClosed(isClosed(dcinput.getSchema(), dcinput.getElement(),
dcinput.getQualifier(), null, dcinput.getVocabulary()));
}
selectableMetadata.add(selMd);
}
@@ -185,7 +187,8 @@ public class SubmissionFormConverter implements DSpaceConverter<DCInputSet, Subm
return INPUT_TYPE_LOOKUP;
}
} 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;
}
}
@@ -203,6 +206,22 @@ public class SubmissionFormConverter implements DSpaceConverter<DCInputSet, Subm
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
public Class<DCInputSet> getModelClass() {
return DCInputSet.class;

View File

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

View File

@@ -7,7 +7,7 @@
*/
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.utils.AuthorityUtils;
import org.dspace.content.authority.ChoiceAuthority;
@@ -23,15 +23,15 @@ import org.springframework.stereotype.Component;
* @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it)
*/
@Component
public class AuthorityRestConverter implements DSpaceConverter<ChoiceAuthority, AuthorityRest> {
public class VocabularyRestConverter implements DSpaceConverter<ChoiceAuthority, VocabularyRest> {
@Override
public AuthorityRest convert(ChoiceAuthority step, Projection projection) {
AuthorityRest authorityRest = new AuthorityRest();
public VocabularyRest convert(ChoiceAuthority authority, Projection projection) {
VocabularyRest authorityRest = new VocabularyRest();
authorityRest.setProjection(projection);
authorityRest.setHierarchical(step.isHierarchical());
authorityRest.setScrollable(step.isScrollable());
authorityRest.setIdentifier(step.hasIdentifier());
authorityRest.setHierarchical(authority.isHierarchical());
authorityRest.setScrollable(authority.isScrollable());
authorityRest.setPreloadLevel(authority.getPreloadLevel());
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 {
private String type;
private String regex;
private AuthorityRest authority;
public String getType() {
return type;
@@ -39,11 +38,4 @@ public class SubmissionFormInputTypeRest {
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 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)
*/
public class AuthorityEntryRest extends RestAddressableModel {
public static final String NAME = "authorityEntry";
private String id;
public class VocabularyEntryRest implements RestModel {
public static final String NAME = "vocabularyEntry";
@JsonInclude(Include.NON_NULL)
private String authority;
private String display;
private String value;
private Map<String, String> otherInformation;
/**
* The Vocabulary Entry Details resource if available related to this entry
*/
@JsonIgnore
private String authorityName;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
private VocabularyEntryDetailsRest vocabularyEntryDetailsRest;
public String getDisplay() {
return display;
@@ -59,31 +57,24 @@ public class AuthorityEntryRest extends RestAddressableModel {
this.value = value;
}
public static String getName() {
return NAME;
public void setAuthority(String authority) {
this.authority = authority;
}
public String getAuthorityName() {
return authorityName;
public String getAuthority() {
return authority;
}
public void setAuthorityName(String authorityName) {
this.authorityName = authorityName;
public void setVocabularyEntryDetailsRest(VocabularyEntryDetailsRest vocabularyEntryDetailsRest) {
this.vocabularyEntryDetailsRest = vocabularyEntryDetailsRest;
}
@Override
public String getCategory() {
return AuthorityRest.CATEGORY;
public VocabularyEntryDetailsRest getVocabularyEntryDetailsRest() {
return vocabularyEntryDetailsRest;
}
@Override
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;
/**
* The authority REST resource
* The vocabulary REST resource
*
* @author Andrea Bollini (andrea.bollini at 4science.it)
*/
@LinksRest(links = {
@LinkRest(name = AuthorityRest.ENTRIES,
method = "query"
@LinkRest(name = VocabularyRest.ENTRIES,
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 CATEGORY = RestAddressableModel.INTEGRATION;
public static final String NAME = "vocabulary";
public static final String CATEGORY = RestAddressableModel.SUBMISSION;
public static final String ENTRIES = "entries";
public static final String ENTRY = "entryValues";
private String name;
@@ -36,7 +31,7 @@ public class AuthorityRest extends BaseObjectRest<String> {
private boolean hierarchical;
private boolean identifier;
private Integer preloadLevel;
@Override
public String getId() {
@@ -67,6 +62,14 @@ public class AuthorityRest extends BaseObjectRest<String> {
this.hierarchical = hierarchical;
}
public Integer getPreloadLevel() {
return preloadLevel;
}
public void setPreloadLevel(Integer preloadLevel) {
this.preloadLevel = preloadLevel;
}
@Override
public String getType() {
return NAME;
@@ -81,12 +84,4 @@ public class AuthorityRest extends BaseObjectRest<String> {
public String getCategory() {
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;
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.utils.Utils;
@@ -17,13 +17,10 @@ import org.dspace.app.rest.utils.Utils;
*
* @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it)
*/
@RelNameDSpaceResource(AuthorityRest.NAME)
public class AuthorityResource extends DSpaceResource<AuthorityRest> {
public AuthorityResource(AuthorityRest sd, Utils utils) {
@RelNameDSpaceResource(VocabularyRest.NAME)
public class VocabularyResource extends DSpaceResource<VocabularyRest> {
public VocabularyResource(VocabularyRest sd, Utils utils) {
super(sd, utils);
if (sd.hasIdentifier()) {
add(utils.linkToSubResource(sd, AuthorityRest.ENTRY));
}
add(utils.linkToSubResource(sd, AuthorityRest.ENTRIES));
add(utils.linkToSubResource(sd, VocabularyRest.ENTRIES));
}
}

View File

@@ -7,6 +7,9 @@
*/
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
* used as inline object in the InputForm resource.
@@ -24,7 +27,9 @@ package org.dspace.app.rest.model.submit;
public class SelectableMetadata {
private String metadata;
private String label;
private String authority;
@JsonInclude(Include.NON_NULL)
private String controlledVocabulary;
@JsonInclude(Include.NON_NULL)
private Boolean closed = false;
public String getMetadata() {
@@ -43,12 +48,12 @@ public class SelectableMetadata {
this.label = label;
}
public void setAuthority(String authority) {
this.authority = authority;
public void setControlledVocabulary(String vocabularyName) {
this.controlledVocabulary = vocabularyName;
}
public String getAuthority() {
return authority;
public String getControlledVocabulary() {
return controlledVocabulary;
}
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;
import org.apache.commons.lang3.StringUtils;
import org.dspace.app.rest.converter.ConverterService;
import org.dspace.app.rest.model.AuthorityEntryRest;
import org.dspace.app.rest.model.AuthorityRest;
import org.dspace.app.rest.model.VocabularyEntryDetailsRest;
import org.dspace.app.rest.model.VocabularyEntryRest;
import org.dspace.app.rest.model.VocabularyRest;
import org.dspace.app.rest.projection.Projection;
import org.dspace.content.authority.Choice;
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_AUTHORLOOKUP = "authorLookup";
public static final String PRESENTATION_TYPE_SUGGEST = "suggest";
public static final String RESERVED_KEYMAP_PARENT = "parent";
@@ -39,11 +43,11 @@ public class AuthorityUtils {
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) {
return cas.getChoiceAuthorityName(schema, element, qualifier);
return cas.getChoiceAuthorityName(schema, element, qualifier, null);
}
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}.
* @return
*/
public AuthorityEntryRest convertEntry(Choice choice, String authorityName, Projection projection) {
AuthorityEntryRest entry = converter.toRest(choice, projection);
entry.setAuthorityName(authorityName);
public VocabularyEntryDetailsRest convertEntryDetails(Choice choice, String authorityName,
boolean isHierarchical, Projection projection) {
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;
}
@@ -76,8 +116,8 @@ public class AuthorityUtils {
* @param projection the projecton to use.
* @return
*/
public AuthorityRest convertAuthority(ChoiceAuthority source, String authorityName, Projection projection) {
AuthorityRest result = converter.toRest(source, projection);
public VocabularyRest convertAuthority(ChoiceAuthority source, String authorityName, Projection projection) {
VocabularyRest result = converter.toRest(source, projection);
result.setName(authorityName);
return result;
}

View File

@@ -28,7 +28,8 @@ public class RegexUtils {
* identifier (digits or uuid)
*/
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

View File

@@ -47,7 +47,6 @@ import org.apache.log4j.Logger;
import org.dspace.app.rest.converter.ConverterService;
import org.dspace.app.rest.exception.PaginationException;
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.CommunityRest;
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.RestModel;
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.HALResource;
import org.dspace.app.rest.projection.CompositeProjection;
@@ -254,7 +254,7 @@ public class Utils {
return CommunityRest.NAME;
}
if (modelPlural.equals("authorities")) {
return AuthorityRest.NAME;
return VocabularyRest.NAME;
}
if (modelPlural.equals("resourcepolicies")) {
return ResourcePolicyRest.NAME;
@@ -268,6 +268,9 @@ public class Utils {
if (StringUtils.equals(modelPlural, "properties")) {
return PropertyRest.NAME;
}
if (StringUtils.equals(modelPlural, "vocabularies")) {
return VocabularyRest.NAME;
}
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"
.andExpect(content().contentType(contentType))
//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.bitstreams.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.util.DCInputsReaderException;
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.services.ConfigurationService;
import org.hamcrest.Matchers;
@@ -39,6 +42,10 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
private ConfigurationService configurationService;
@Autowired
private SubmissionFormRestRepository submissionFormRestRepository;
@Autowired
private PluginService pluginService;
@Autowired
private ChoiceAuthorityService cas;
@Test
public void findAll() throws Exception {
@@ -56,15 +63,15 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.andExpect(status().isOk())
//We expect the content type to be "application/hal+json;charset=UTF-8"
.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.totalElements", equalTo(4)))
.andExpect(jsonPath("$.page.totalElements", equalTo(6)))
.andExpect(jsonPath("$.page.totalPages", equalTo(1)))
.andExpect(jsonPath("$.page.number", is(0)))
.andExpect(
jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL + "config/submissionforms")))
//The array of submissionforms should have a size of 3
.andExpect(jsonPath("$._embedded.submissionforms", hasSize(equalTo(4))))
//The array of submissionforms should have a size of 6
.andExpect(jsonPath("$._embedded.submissionforms", hasSize(equalTo(6))))
;
}
@@ -75,12 +82,12 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.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.number", is(0)))
.andExpect(jsonPath("$._links.self.href", Matchers.startsWith(REST_SERVER_URL
+ "config/submissionforms")))
.andExpect(jsonPath("$._embedded.submissionforms", hasSize(equalTo(4))));
.andExpect(jsonPath("$._embedded.submissionforms", hasSize(equalTo(6))));
}
@Test
@@ -152,6 +159,121 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
"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
public void findOpenRelationshipConfig() throws Exception {
String token = getAuthToken(admin.getEmail(), password);
@@ -203,8 +325,14 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
public void languageSupportTest() throws Exception {
context.turnOffAuthorisationSystem();
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");
@@ -234,7 +362,8 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
"Selezionare la lingua del contenuto principale dell'item."
+ " Se la lingua non compare nell'elenco, selezionare (Altro)."
+ " 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
getClient(tokenEperson).perform(get("/api/config/submissionforms/languagetest").locale(uk))
@@ -256,9 +385,8 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.matchFormFieldDefinition("dropdown", "Мова", null, false,
"Виберiть мову головного змiсту файлу, як що мови немає у списку, вибрати (Iнша)."
+ " Як що вмiст вайлу не є текстовим, наприклад є фотографiєю, тодi вибрати (N/A)",
"dc.language.iso"))));
resetLocalesConfiguration();
null, "dc.language.iso", "common_iso_languages"))));
resetLocalesConfiguration();
}
@Test
@@ -266,8 +394,14 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
context.turnOffAuthorisationSystem();
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();
EPerson epersonIT = EPersonBuilder.createEPerson(context)
.withEmail("epersonIT@example.com")
@@ -307,7 +441,8 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
"Selezionare la lingua del contenuto principale dell'item."
+ " Se la lingua non compare nell'elenco, selezionare (Altro)."
+ " 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
getClient(tokenEpersonUK).perform(get("/api/config/submissionforms/languagetest"))
@@ -329,9 +464,8 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.matchFormFieldDefinition("dropdown", "Мова", null, false,
"Виберiть мову головного змiсту файлу, як що мови немає у списку, вибрати (Iнша)."
+ " Як що вмiст вайлу не є текстовим, наприклад є фотографiєю, тодi вибрати (N/A)",
"dc.language.iso"))));
resetLocalesConfiguration();
null, "dc.language.iso", "common_iso_languages"))));
resetLocalesConfiguration();
}
@Test
@@ -339,8 +473,14 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
context.turnOffAuthorisationSystem();
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 it = new Locale("it");
@@ -375,9 +515,9 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
"Selezionare la lingua del contenuto principale dell'item."
+ " Se la lingua non compare nell'elenco, selezionare (Altro)."
+ " Se il contenuto non ha davvero una lingua"
+ " (ad esempio, se è un set di dati o un'immagine) selezionare (N/A)", "dc.language.iso"))));
resetLocalesConfiguration();
+ " (ad esempio, se è un set di dati o un'immagine) selezionare (N/A)",
null, "dc.language.iso", "common_iso_languages"))));
resetLocalesConfiguration();
}
@Test
@@ -387,7 +527,12 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
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();
context.restoreAuthSystemState();
@@ -407,8 +552,7 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
.matchFormFieldDefinition("onebox", "Titolo",
"\u00C8 necessario inserire un titolo principale per questo item", false,
"Inserisci titolo principale di questo item", "dc.title"))));
resetLocalesConfiguration();
resetLocalesConfiguration();
}
@Test
@@ -417,7 +561,12 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
String[] supportedLanguage = {"it","uk","en"};
configurationService.setProperty("default.locale","en");
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();
context.restoreAuthSystemState();
@@ -446,5 +595,8 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe
configurationService.setProperty("default.locale","en");
configurationService.setProperty("webui.supported.locales",null);
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
* {@link SubmissionFormFieldMatcher#matchFormFieldDefinition(String, String, String, boolean, String, String, String)}
* with a null style
* {@link SubmissionFormFieldMatcher#matchFormFieldDefinition(String, String, String, boolean, String, String, String, String)}
* with a null style and vocabulary name
*
* @param 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
* the expected input type
@@ -74,13 +76,45 @@ public class SubmissionFormFieldMatcher {
* @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) {
boolean repeatable,
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(
// check each field definition
hasJsonPath("$.input.type", is(type)),
hasJsonPath("$.label", containsString(label)),
hasJsonPath("$.selectableMetadata[0].metadata", is(metadata)),
controlledVocabulary != null ? hasJsonPath("$.selectableMetadata[0].controlledVocabulary",
is(controlledVocabulary)) : hasNoJsonPath("$.selectableMetadata[0].controlledVocabulary"),
mandatoryMessage != null ? hasJsonPath("$.mandatoryMessage", containsString(mandatoryMessage)) :
hasNoJsonPath("$.mandatoryMessage"),
hasJsonPath("$.mandatory", is(mandatoryMessage != null)),

View File

@@ -8,7 +8,6 @@
package org.dspace.app.rest.matcher;
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.containsString;
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
*/
public class AuthorityEntryMatcher {
public class VocabularyEntryDetailsMatcher {
private AuthorityEntryMatcher() {
private VocabularyEntryDetailsMatcher() {
}
public static Matcher<? super Object> matchAuthorityEntry(String id, String display, String value) {
return allOf(
matchProperties(id, display, value),
matchLinks());
matchLinks(id));
}
public static Matcher<? super Object> matchLinks() {
public static Matcher<? super Object> matchLinks(String id) {
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) {
@@ -39,16 +38,7 @@ public class AuthorityEntryMatcher {
hasJsonPath("$.id", is(id)),
hasJsonPath("$.display", is(display)),
hasJsonPath("$.value", is(value)),
hasJsonPath("$.type", is("authority"))
);
}
/**
* Gets a matcher for all expected embeds when the full projection is requested.
*/
public static Matcher<? super Object> matchFullEmbeds() {
return matchEmbeds(
"authorityEntries"
hasJsonPath("$.type", is("vocabularyEntryDetail"))
);
}
}

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:attribute name="id" type="xs:ID" use="optional"/>
<xs:attribute name="label" type="xs:string" use="required"/>
<xs:attribute name="selectable" type="xs:boolean" default="true"/>
</xs:complexType>
</xs:element>
</xs:schema>

View File

@@ -1486,7 +1486,7 @@ orcid.url = https://orcid.org/
## eg: nsi, srsc.
## Each DSpaceControlledVocabulary plugin comes with three configuration options:
# 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: "::"
##
## An example using "srsc" can be found later in this section