Skip to content

Commit

Permalink
UFAL/Enhanced type-bind feature (#762)
Browse files Browse the repository at this point in the history
* type bind is correctly rendered in the FE, but BE is still not working

* Synchronized the `submission-forms_cs.xml`

* Added doc into `submission-forms` about enhanced type-bind `field`

* Updated `local.cfg` for tests - added type-bind property

* Updated docs for the customized type-bind configuration property.

* Updated MetadataValidation following the type-bind customization.

* Added isAllowed function for multiple type-bind definitions

* Added some docs for the new method

* The values of the input wasn't loaded.

* Allowed fields could be empty when they should have values.

* Used isEmpty function and created constant for the `=>`.
  • Loading branch information
milanmajchrak authored Oct 17, 2024
1 parent 999164c commit 6c6de87
Show file tree
Hide file tree
Showing 10 changed files with 1,680 additions and 478 deletions.
36 changes: 30 additions & 6 deletions dspace-api/src/main/java/org/dspace/app/util/DCInput.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.xml.sax.SAXException;

Expand Down Expand Up @@ -249,12 +250,11 @@ public DCInput(Map<String, String> fieldMap, Map<String, List<String>> listMap,
// parsing of the <type-bind> element (using the colon as split separator)
typeBind = new ArrayList<String>();
String typeBindDef = fieldMap.get("type-bind");
if (typeBindDef != null && typeBindDef.trim().length() > 0) {
String[] types = typeBindDef.split(",");
for (String type : types) {
typeBind.add(type.trim());
}
}
this.insertToTypeBind(typeBindDef);
String typeBindField = fieldMap.get(DCInputsReader.TYPE_BIND_FIELD_ATTRIBUTE);
this.insertToTypeBind(typeBindField);


style = fieldMap.get("style");
isRelationshipField = fieldMap.containsKey("relationship-type");
isMetadataField = fieldMap.containsKey("dc-schema");
Expand All @@ -273,6 +273,15 @@ public DCInput(Map<String, String> fieldMap, Map<String, List<String>> listMap,

}

private void insertToTypeBind(String typeBindDef) {
if (StringUtils.isNotEmpty(typeBindDef)) {
String[] types = typeBindDef.split(",");
for (String type : types) {
typeBind.add(type.trim());
}
}
}

protected void initRegex(String regex) {
this.regex = null;
this.pattern = null;
Expand Down Expand Up @@ -559,6 +568,21 @@ public boolean isAllowedFor(String typeName) {
return typeBind.contains(typeName);
}

/**
* Decides if this field is valid for the document type
* Check if one of the typeName is in the typeBind list
*
* @param typeNames List of document type names e.g. ["VIDEO"]
* @return true when there is no type restriction or typeName is allowed
*/
public boolean isAllowedFor(List<String> typeNames) {
if (typeBind.isEmpty()) {
return true;
}

return CollectionUtils.containsAny(typeBind, typeNames);
}

public String getScope() {
return visibility;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public class DCInputsReader {
* Keyname for storing the name of the complex input type
*/
static final String COMPLEX_DEFINITION_REF = "complex-definition-ref";
public static final String TYPE_BIND_FIELD_ATTRIBUTE = "field";


/**
Expand Down
6 changes: 5 additions & 1 deletion dspace-api/src/test/data/dspaceFolder/config/local.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -306,4 +306,8 @@ sync.storage.service.enabled = false
signposting.enabled = true

# Test configuration has only EN locale (submission-forms.xml)
webui.supported.locales = en
webui.supported.locales = en

# Type bind configuration for the submission form with special type-bind field
# When title is something like "Type-bind test" the type-bind field will popped up
submit.type-bind.field = dc.type,dc.identifier.citation=>dc.title
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@
*/
public class DescribeStep extends AbstractProcessingStep {

public static final String KEY_VALUE_SEPARATOR = "=>";

private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(DescribeStep.class);

// Input reader for form configuration
Expand Down Expand Up @@ -93,15 +95,34 @@ public DataDescribe getData(SubmissionService submissionService, InProgressSubmi

private void readField(InProgressSubmission obj, SubmissionStepConfig config, DataDescribe data,
DCInputSet inputConfig) throws DCInputsReaderException {
String documentTypeValue = "";
List<MetadataValue> documentType = itemService.getMetadataByMetadataString(obj.getItem(),
configurationService.getProperty("submit.type-bind.field", "dc.type"));
if (documentType.size() > 0) {
documentTypeValue = documentType.get(0).getValue();
List<String> documentTypeValueList = new ArrayList<>();
List<String> typeBindFields = Arrays.asList(
configurationService.getArrayProperty("submit.type-bind.field", new String[0]));

for (String typeBindField : typeBindFields) {
String typeBFKey = typeBindField;
if (typeBindField.contains(KEY_VALUE_SEPARATOR)) {
String[] parts = typeBindField.split(KEY_VALUE_SEPARATOR);
// Get the second part of the split - the metadata field
typeBFKey = parts[1];
}
List<MetadataValue> documentType = itemService.getMetadataByMetadataString(obj.getItem(), typeBFKey);
if (documentType.size() > 0) {
documentTypeValueList.add(documentType.get(0).getValue());
}
}

// Get list of all field names (including qualdrop names) allowed for this dc.type
List<String> allowedFieldNames = inputConfig.populateAllowedFieldNames(documentTypeValue);
List<String> allowedFieldNames = new ArrayList<>();

if (CollectionUtils.isEmpty(documentTypeValueList)) {
// If no dc.type is set, we allow all fields
allowedFieldNames.addAll(inputConfig.populateAllowedFieldNames(null));
} else {
documentTypeValueList.forEach(documentTypeValue -> {
allowedFieldNames.addAll(inputConfig.populateAllowedFieldNames(documentTypeValue));
});
}

// Loop input rows and process submitted metadata
for (DCInput[] row : inputConfig.getFields()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
*/
package org.dspace.app.rest.submit.step.validation;

import static org.dspace.app.rest.submit.step.DescribeStep.KEY_VALUE_SEPARATOR;

import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
Expand Down Expand Up @@ -68,22 +70,46 @@ public List<ErrorRest> validate(SubmissionService submissionService, InProgressS
SubmissionStepConfig config) throws DCInputsReaderException, SQLException {

List<ErrorRest> errors = new ArrayList<>();
String documentTypeValue = "";
DCInputSet inputConfig = getInputReader().getInputsByFormName(config.getId());
List<MetadataValue> documentType = itemService.getMetadataByMetadataString(obj.getItem(),
configurationService.getProperty("submit.type-bind.field", "dc.type"));
if (documentType.size() > 0) {
documentTypeValue = documentType.get(0).getValue();
List<String> documentTypeValueList = new ArrayList<>();
// Get the list of type-bind fields. It could be in the form of schema.element.qualifier=>metadata_field, or
// just metadata_field
List<String> typeBindFields = Arrays.asList(
configurationService.getArrayProperty("submit.type-bind.field", new String[0]));

for (String typeBindField : typeBindFields) {
String typeBFKey = typeBindField;
// If the type-bind field is in the form of schema.element.qualifier=>metadata_field, split it and get the
// metadata field
if (typeBindField.contains(KEY_VALUE_SEPARATOR)) {
String[] parts = typeBindField.split(KEY_VALUE_SEPARATOR);
// Get the second part of the split - the metadata field
typeBFKey = parts[1];
}
// Get the metadata value for the type-bind field
List<MetadataValue> documentType = itemService.getMetadataByMetadataString(obj.getItem(), typeBFKey);
if (documentType.size() > 0) {
documentTypeValueList.add(documentType.get(0).getValue());
}
}

// Get list of all field names (including qualdrop names) allowed for this dc.type
List<String> allowedFieldNames = inputConfig.populateAllowedFieldNames(documentTypeValue);
// Get list of all field names (including qualdrop names) allowed for this dc.type, or specific type-bind field
List<String> allowedFieldNames = new ArrayList<>();

if (CollectionUtils.isEmpty(documentTypeValueList)) {
// If no dc.type is set, we allow all fields
allowedFieldNames.addAll(inputConfig.populateAllowedFieldNames(null));
} else {
documentTypeValueList.forEach(documentTypeValue -> {
allowedFieldNames.addAll(inputConfig.populateAllowedFieldNames(documentTypeValue));
});
}

// Begin the actual validation loop
for (DCInput[] row : inputConfig.getFields()) {
for (DCInput input : row) {
String fieldKey =
metadataAuthorityService.makeFieldKey(input.getSchema(), input.getElement(), input.getQualifier());
metadataAuthorityService.makeFieldKey(input.getSchema(), input.getElement(),
input.getQualifier());
boolean isAuthorityControlled = metadataAuthorityService.isAuthorityControlled(fieldKey);

List<String> fieldsName = new ArrayList<String>();
Expand All @@ -99,10 +125,10 @@ public List<ErrorRest> validate(SubmissionService submissionService, InProgressS

// Check the lookup list. If no other inputs of the same field name allow this type,
// then remove. This includes field name without qualifier.
if (!input.isAllowedFor(documentTypeValue) && (!allowedFieldNames.contains(fullFieldname)
if (!input.isAllowedFor(documentTypeValueList) && (!allowedFieldNames.contains(fullFieldname)
&& !allowedFieldNames.contains(input.getFieldName()))) {
itemService.removeMetadataValues(ContextUtil.obtainCurrentRequestContext(),
obj.getItem(), mdv);
obj.getItem(), mdv);
} else {
validateMetadataValues(mdv, input, config, isAuthorityControlled, fieldKey, errors);
if (mdv.size() > 0 && input.isVisible(DCInput.SUBMISSION_SCOPE)) {
Expand All @@ -126,34 +152,34 @@ public List<ErrorRest> validate(SubmissionService submissionService, InProgressS
for (String fieldName : fieldsName) {
boolean valuesRemoved = false;
List<MetadataValue> mdv = itemService.getMetadataByMetadataString(obj.getItem(), fieldName);
if (!input.isAllowedFor(documentTypeValue)) {
if (!input.isAllowedFor(documentTypeValueList)) {
// Check the lookup list. If no other inputs of the same field name allow this type,
// then remove. Otherwise, do not
if (!(allowedFieldNames.contains(fieldName))) {
itemService.removeMetadataValues(ContextUtil.obtainCurrentRequestContext(),
obj.getItem(), mdv);
valuesRemoved = true;
log.debug("Stripping metadata values for " + input.getFieldName() + " on type "
+ documentTypeValue + " as it is allowed by another input of the same field " +
+ documentTypeValueList + " as it is allowed by another input of the same field " +
"name");
} else {
log.debug("Not removing unallowed metadata values for " + input.getFieldName() + " on type "
+ documentTypeValue + " as it is allowed by another input of the same field " +
+ documentTypeValueList + " as it is allowed by another input of the same field " +
"name");
}
}
validateMetadataValues(mdv, input, config, isAuthorityControlled, fieldKey, errors);
if (((input.isRequired() && mdv.size() == 0) && input.isVisible(DCInput.SUBMISSION_SCOPE)
&& !valuesRemoved)
&& !valuesRemoved)
|| !isValidComplexDefinitionMetadata(input, mdv)) {
// Is the input required for *this* type? In other words, are we looking at a required
// input that is also allowed for this document type
if (input.isAllowedFor(documentTypeValue)) {
if (input.isAllowedFor(documentTypeValueList)) {
// since this field is missing add to list of error
// fields
addError(errors, ERROR_VALIDATION_REQUIRED, "/"
+ WorkspaceItemRestRepository.OPERATION_PATH_SECTIONS + "/" + config.getId() + "/" +
input.getFieldName());
input.getFieldName());
}
}
if (LOCAL_METADATA_HAS_CMDI.equals(fieldName)) {
Expand All @@ -167,7 +193,9 @@ public List<ErrorRest> validate(SubmissionService submissionService, InProgressS
}
}
}

}

return errors;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,4 +320,49 @@ public void shouldCreateProvenanceMessageOnItemSubmit() throws Exception {
}
assertThat(containsSubmitterProvenance, is(true));
}

// When some input field has <type-bind field="something">...</type-bind> in the submission-forms.xml
@Test
public void shouldCreateItemWithCustomTypeBindField() throws Exception {
context.turnOffAuthorisationSystem();
String CITATION_VALUE = "Some citation";

//** GIVEN **
//1. A community with one collection.
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();

// Submitter group - allow deposit a new item without workflow
Collection col1 = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Collection")
.build();

//3. a workspace item
WorkspaceItem wsitem = WorkspaceItemBuilder.createWorkspaceItem(context, col1)
.withTitle("Type-bind test")
.withIssueDate("2017-10-17")
.grantLicense()
.withMetadata("dc", "identifier", "citation", CITATION_VALUE)
.build();

context.restoreAuthSystemState();

// get the submitter auth token
String authToken = getAuthToken(admin.getEmail(), password);

// submit the workspaceitem to start the workflow
getClient(authToken)
.perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems")
.content("/api/submission/workspaceitems/" + wsitem.getID())
.contentType(textUriContentType))
.andExpect(status().isCreated());

// Load deposited item and check the provenance metadata
Item depositedItem = itemService.find(context, wsitem.getItem().getID());
List<MetadataValue> mvList = itemService.getMetadata(depositedItem, "dc", "identifier",
"citation", Item.ANY);
assertFalse(mvList.isEmpty());
assertThat(mvList.get(0).getValue(), is(CITATION_VALUE));
}
}
3 changes: 2 additions & 1 deletion dspace/config/dspace.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -1553,7 +1553,8 @@ request.item.reject.email = true
#------------------SUBMISSION CONFIGURATION------------------------#
#------------------------------------------------------------------#
# Field to use for type binding, default dc.type
submit.type-bind.field = dc.type
# It could be in the form of schema.element.qualifier=>metadata_field, or just metadata_field
submit.type-bind.field = dc.type,dc.language.iso=>edm.type

#---------------------------------------------------------------#
#----------SOLR DATABASE RESYNC SCRIPT CONFIGURATION------------#
Expand Down
3 changes: 3 additions & 0 deletions dspace/config/submission-forms.dtd
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
<!ELEMENT dc-qualifier (#PCDATA) >
<!ELEMENT language (#PCDATA)>
<!ELEMENT type-bind (#PCDATA) >
<!-- When you configure special field for the `type-bind` you must update it in the `submit.type-bind.field`
configuration property like this: `<INPUT_METADATA_FIELD>=><TYPE_BIND_FIELD>` e.g. `dc.language.iso=>edm.type`-->
<!ATTLIST type-bind field CDATA #IMPLIED >

<!ELEMENT repeatable (#PCDATA) >
<!ELEMENT label (#PCDATA) >
Expand Down
7 changes: 5 additions & 2 deletions dspace/config/submission-forms.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1776,14 +1776,17 @@
<required>Please give us a description</required>
</field>
</row>
<!-- When you configure special field for the `type-bind` you must update it in the `submit.type-bind.field`
configuration property like this: `<INPUT_METADATA_FIELD>=><TYPE_BIND_FIELD>`
e.g. `dc.language.iso=>edm.type`-->
<row>
<field>
<dc-schema>dc</dc-schema>
<dc-element>language</dc-element>
<dc-qualifier>iso</dc-qualifier>
<repeatable>true</repeatable>
<label>Pick the languages of the TEXT</label>
<type-bind>TEXT</type-bind>
<type-bind field="edm.type">TEXT</type-bind>
<input-type>autocomplete</input-type>
<hint>Select the language of the main content of the item. Multiple languages are possible. Start
typing the language and use autocomplete form that will appear if applicable. Better to list all the languages then to use the 'mul' iso code (if there are too many, contact support).
Expand All @@ -1798,7 +1801,7 @@
<dc-qualifier>iso</dc-qualifier>
<repeatable>true</repeatable>
<label>Language</label>
<type-bind>VIDEO,IMAGE,SOUND,3D</type-bind>
<type-bind field="edm.type">VIDEO,IMAGE,SOUND,3D</type-bind>
<input-type>autocomplete</input-type>
<hint>Optionally, select the language of the main content
of the item. Multiple languages are possible. Start
Expand Down
Loading

0 comments on commit 6c6de87

Please sign in to comment.