diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java index 84c7f7b779..fe7992a76c 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java @@ -17,8 +17,8 @@ import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; import javax.annotation.Nullable; @@ -38,7 +38,6 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; import org.dspace.content.DSpaceObject; import org.dspace.content.Entity; -import org.dspace.content.EntityType; import org.dspace.content.Item; import org.dspace.content.MetadataField; import org.dspace.content.MetadataSchemaEnum; @@ -106,19 +105,44 @@ public class MetadataImport { protected static final String AC_PREFIX = "authority.controlled."; /** - * Map of field:value to csv row number, used to resolve indirect entity references. + * Map of field:value to csv row number, used to resolve indirect entity target references. * - * @see #populateRefAndRowMap(DSpaceCSVLine, int, UUID) + * @see #populateRefAndRowMap(DSpaceCSVLine, UUID) */ - protected HashMap> csvRefMap = new HashMap<>(); + protected Map> csvRefMap = new HashMap<>(); /** - * Map of csv row number to UUID, used to resolve indirect entity references. + * Map of csv row number to UUID, used to resolve indirect entity target references. * - * @see #populateRefAndRowMap(DSpaceCSVLine, int, UUID) + * @see #populateRefAndRowMap(DSpaceCSVLine, UUID) */ protected HashMap csvRowMap = new HashMap<>(); + /** + * Map of UUIDs to their entity types. + * + * @see #populateRefAndRowMap(DSpaceCSVLine, UUID) + */ + protected static HashMap entityTypeMap = new HashMap<>(); + + /** + * Map of UUIDs to their relations that are referenced within any import with their referers. + * + * @see #populateEntityRelationMap(String, String, String) + */ + protected static HashMap>> entityRelationMap = new HashMap<>(); + + + /** + * Collection of errors generated during relation validation process. + */ + protected ArrayList relationValidationErrors = new ArrayList<>(); + + /** + * Counter of rows proccssed in a CSV. + */ + protected Integer rowCount = 1; + /** * Logger */ @@ -184,10 +208,10 @@ public class MetadataImport { c.setMode(Context.Mode.BATCH_EDIT); // Process each change - int rowCount = 1; + rowCount = 1; for (DSpaceCSVLine line : toImport) { - //Resolve references to other items - populateRefAndRowMap(line, rowCount, null); + // Resolve target references to other items + populateRefAndRowMap(line, line.getID()); line = resolveEntityRefs(line); // Get the DSpace item to compare with UUID id = line.getID(); @@ -201,7 +225,7 @@ public class MetadataImport { WorkflowItem wfItem = null; Item item = null; - // Is this a new item? + // Is this an existing item? if (id != null) { // Get the item item = itemService.find(c, id); @@ -238,9 +262,8 @@ public class MetadataImport { } } } - // Compare - compare(item, fromCSV, change, md, whatHasChanged, line); + compareAndUpdate(item, fromCSV, change, md, whatHasChanged, line); } } @@ -378,30 +401,23 @@ public class MetadataImport { item = wsItem.getItem(); // Add the metadata to the item - List relationships = new LinkedList<>(); + for (BulkEditMetadataValue dcv : whatHasChanged.getAdds()) { + itemService.addMetadata(c, item, dcv.getSchema(), + dcv.getElement(), + dcv.getQualifier(), + dcv.getLanguage(), + dcv.getValue(), + dcv.getAuthority(), + dcv.getConfidence()); + } + //Add relations after all metadata has been processed for (BulkEditMetadataValue dcv : whatHasChanged.getAdds()) { if (StringUtils.equals(dcv.getSchema(), MetadataSchemaEnum.RELATION.getName())) { - - if (!StringUtils.equals(dcv.getElement(), "type")) { - relationships.add(dcv); - } else { - handleRelationshipMetadataValueFromBulkEditMetadataValue(item, dcv); - } - - } else { - itemService.addMetadata(c, item, dcv.getSchema(), - dcv.getElement(), - dcv.getQualifier(), - dcv.getLanguage(), - dcv.getValue(), - dcv.getAuthority(), - dcv.getConfidence()); + addRelationship(c, item, dcv.getElement(), dcv.getValue()); } } - for (BulkEditMetadataValue relationship : relationships) { - handleRelationshipMetadataValueFromBulkEditMetadataValue(item, relationship); - } + // Should the workflow be used? if (useWorkflow) { WorkflowService workflowService = WorkflowServiceFactory.getInstance().getWorkflowService(); @@ -424,8 +440,6 @@ public class MetadataImport { } } - // Commit changes to the object -// c.commit(); whatHasChanged.setItem(item); } @@ -439,7 +453,7 @@ public class MetadataImport { c.uncacheEntity(wfItem); c.uncacheEntity(item); } - populateRefAndRowMap(line, rowCount, item == null ? null : item.getID()); + populateRefAndRowMap(line, item == null ? null : item.getID()); // keep track of current rows processed rowCount++; } @@ -452,32 +466,14 @@ public class MetadataImport { } // Return the changes + if (!change ) { + validateExpressedRelations(); + } return changes; } - /** - * This metod handles the BulkEditMetadataValue objects that correspond to Relationship metadatavalues - * @param item The item to which this metadatavalue will belong - * @param dcv The BulkEditMetadataValue to be processed - * @throws SQLException If something goes wrong - * @throws AuthorizeException If something goes wrong - */ - private void handleRelationshipMetadataValueFromBulkEditMetadataValue(Item item, BulkEditMetadataValue dcv) - throws SQLException, AuthorizeException, MetadataImportException { - LinkedList values = new LinkedList<>(); - values.add(dcv.getValue()); - LinkedList authorities = new LinkedList<>(); - authorities.add(dcv.getAuthority()); - LinkedList confidences = new LinkedList<>(); - confidences.add(dcv.getConfidence()); - handleRelationMetadata(c, item, dcv.getSchema(), dcv.getElement(), - dcv.getQualifier(), - dcv.getLanguage(), values, authorities, confidences); - } - - /** - * Compare an item metadata with a line from CSV, and optionally update the item + * Compare an item metadata with a line from CSV, and optionally update the item. * * @param item The current item metadata * @param fromCSV The metadata from the CSV file @@ -487,9 +483,10 @@ public class MetadataImport { * @param line line in CSV file * @throws SQLException if there is a problem accessing a Collection from the database, from its handle * @throws AuthorizeException if there is an authorization problem with permissions + * @throws MetadataImportException custom exception for error handling within metadataimport */ - protected void compare(Item item, String[] fromCSV, boolean change, - String md, BulkEditChange changes, DSpaceCSVLine line) + protected void compareAndUpdate(Item item, String[] fromCSV, boolean change, + String md, BulkEditChange changes, DSpaceCSVLine line) throws SQLException, AuthorizeException, MetadataImportException { // Log what metadata element we're looking at String all = ""; @@ -663,7 +660,6 @@ public class MetadataImport { } } - if (StringUtils.equals(schema, MetadataSchemaEnum.RELATION.getName())) { List relationshipTypeList = relationshipTypeService .findByLeftwardOrRightwardTypeName(c, element); @@ -674,7 +670,7 @@ public class MetadataImport { relationshipService.update(c, relationship); } } - handleRelationMetadata(c, item, schema, element, qualifier, language, values, authorities, confidences); + addRelationships(c, item, element, values); } else { itemService.clearMetadata(c, item, schema, element, qualifier, language); itemService.addMetadata(c, item, schema, element, qualifier, @@ -685,47 +681,35 @@ public class MetadataImport { } /** - * This method decides whether the metadatavalue is of type relation.type or if it corresponds to - * a relationship and handles it accordingly to their respective methods + * + * Adds multiple relationships with a matching typeName to an item. + * * @param c The relevant DSpace context * @param item The item to which this metadatavalue belongs to - * @param schema The schema for the metadatavalue - * @param element The element for the metadatavalue - * @param qualifier The qualifier for the metadatavalue - * @param language The language for the metadatavalue - * @param values The values for the metadatavalue - * @param authorities The authorities for the metadatavalue - * @param confidences The confidences for the metadatavalue + * @param typeName The element for the metadatavalue + * @param values to iterate over * @throws SQLException If something goes wrong * @throws AuthorizeException If something goes wrong */ - private void handleRelationMetadata(Context c, Item item, String schema, String element, String qualifier, - String language, List values, List authorities, - List confidences) throws SQLException, AuthorizeException, + private void addRelationships(Context c, Item item, String typeName, List values) + throws SQLException, AuthorizeException, MetadataImportException { - - if (StringUtils.equals(element, "type") && StringUtils.isBlank(qualifier)) { - handleRelationTypeMetadata(c, item, schema, element, qualifier, language, values, authorities, confidences); - - } else { - for (String value : values) { - handleRelationOtherMetadata(c, item, element, value); - } + for (String value : values) { + addRelationship(c, item, typeName, value); } - } /** - * Gets an existing entity from a reference. + * Gets an existing entity from a target reference. * * @param context the context to use. - * @param reference the reference which may be a UUID, metadata reference, or rowName reference. + * @param targetReference the target reference which may be a UUID, metadata reference, or rowName reference. * @return the entity, which is guaranteed to exist. - * @throws MetadataImportException if the reference is badly formed or refers to a non-existing item. + * @throws MetadataImportException if the target reference is badly formed or refers to a non-existing item. */ - private Entity getEntity(Context context, String reference) throws MetadataImportException { + private Entity getEntity(Context context, String targetReference) throws MetadataImportException { Entity entity = null; - UUID uuid = resolveEntityRef(context, reference); + UUID uuid = resolveEntityRef(context, targetReference); // At this point, we have a uuid, so we can get an entity try { entity = entityService.findByItemId(context, uuid); @@ -734,218 +718,73 @@ public class MetadataImport { } return entity; } catch (SQLException sqle) { - throw new MetadataImportException("Unable to find entity using reference: " + reference, sqle); + throw new MetadataImportException("Unable to find entity using reference: " + targetReference, sqle); } } /** - * This method takes the item, element and values to determine what relationships should be built - * for these parameters and calls on the method to construct them + * + * Creates a relationship for the given item + * * @param c The relevant DSpace context * @param item The item that the relationships will be made for - * @param element The string determining which relationshiptype is to be used + * @param typeName The relationship typeName * @param value The value for the relationship * @throws SQLException If something goes wrong * @throws AuthorizeException If something goes wrong */ - private void handleRelationOtherMetadata(Context c, Item item, String element, String value) + private void addRelationship(Context c, Item item, String typeName, String value) throws SQLException, AuthorizeException, MetadataImportException { if (value.isEmpty()) { return; } - Entity entity = entityService.findByItemId(c, item.getID()); boolean left = false; - List acceptableRelationshipTypes = new LinkedList<>(); + // Get entity from target reference Entity relationEntity = getEntity(c, value); + // Get relationship type of entity and item + String relationEntityRelationshipType = itemService.getMetadata(relationEntity.getItem(), + "relationship", "type", + null, Item.ANY).get(0).getValue(); + String itemRelationshipType = itemService.getMetadata(item, "relationship", "type", + null, Item.ANY).get(0).getValue(); - List leftRelationshipTypesForEntity = entityService.getLeftRelationshipTypes(c, entity); - List rightRelationshipTypesForEntity = entityService.getRightRelationshipTypes(c, entity); + // Get the correct RelationshipType based on typeName + List relType = relationshipTypeService.findByLeftwardOrRightwardTypeName(c, typeName); + RelationshipType foundRelationshipType = matchRelationshipType(relType, + relationEntityRelationshipType, itemRelationshipType, typeName); - //Identify which RelationshipType objects match the combination of: - // * the left entity type - // * the right entity type - // * the name of the relationship type (based on the expected direction of the relationship) - //The matches are included in the acceptableRelationshipTypes - for (RelationshipType relationshipType : entityService.getAllRelationshipTypes(c, entity)) { - if (StringUtils.equalsIgnoreCase(relationshipType.getLeftwardType(), element)) { - left = verifyValidLeftwardRelationshipType(c, entity, relationEntity, left, - acceptableRelationshipTypes, - leftRelationshipTypesForEntity, - relationshipType); - } else if (StringUtils.equalsIgnoreCase(relationshipType.getRightwardType(), element)) { - left = verifyValidRightwardRelationshipType(c, entity, relationEntity, left, - acceptableRelationshipTypes, - rightRelationshipTypesForEntity, - relationshipType); - } + if (foundRelationshipType == null) { + throw new MetadataImportException("Error on CSV row " + rowCount + ":" + "\n" + + "No Relationship type found for:\n" + + "Target type: " + relationEntityRelationshipType + "\n" + + "Origin referer type: " + itemRelationshipType + "\n" + + "with typeName: " + typeName); } - if (acceptableRelationshipTypes.size() > 1) { - log.error("Ambiguous relationship_types were found"); - return; - } - if (acceptableRelationshipTypes.size() == 0) { - log.error("no relationship_types were found"); - return; + if (foundRelationshipType.getLeftwardType().equalsIgnoreCase(typeName)) { + left = true; } - //There is exactly one - buildRelationObject(c, item, relationEntity.getItem(), left, acceptableRelationshipTypes.get(0)); - } - - /** - * This method creates the relationship for the item and stores it in the database - * @param c The relevant DSpace context - * @param item The item for which this relationship will be constructed - * @param otherItem The item for the relationship - * @param left A boolean indicating whether the item is the leftItem or the rightItem - * @param acceptedRelationshipType The acceptable relationship type - * @throws SQLException If something goes wrong - * @throws AuthorizeException If something goes wrong - */ - private void buildRelationObject(Context c, Item item, Item otherItem, boolean left, - RelationshipType acceptedRelationshipType) - throws SQLException, AuthorizeException { + // Placeholder items for relation placing Item leftItem = null; Item rightItem = null; if (left) { leftItem = item; - rightItem = otherItem; + rightItem = relationEntity.getItem(); } else { + leftItem = relationEntity.getItem(); rightItem = item; - leftItem = otherItem; } - RelationshipType relationshipType = acceptedRelationshipType; - int leftPlace = relationshipService.findNextLeftPlaceByLeftItem(c, leftItem) ; - int rightPlace = relationshipService.findNextRightPlaceByRightItem(c, rightItem); + + // Create the relationship + int leftPlace = relationshipService.findNextLeftPlaceByLeftItem(c, leftItem); + int rightPlace = relationshipService.findNextRightPlaceByRightItem(c, rightItem)1; Relationship persistedRelationship = relationshipService.create(c, leftItem, rightItem, - relationshipType, leftPlace, rightPlace); + foundRelationshipType, leftPlace, rightPlace); relationshipService.update(c, persistedRelationship); } - /** - * This method will add RelationshipType objects to the acceptableRelationshipTypes list - * if applicable and valid RelationshipType objects are found. It will also return a boolean indicating - * whether we're dealing with a left Relationship or not - * @param c The relevant DSpace context - * @param entity The Entity for which the RelationshipType has to be checked - * @param relationEntity The other Entity of the Relationship - * @param left Boolean indicating whether the Relationship is left or not - * @param acceptableRelationshipTypes The list of RelationshipType objects that will be added to - * @param rightRelationshipTypesForEntity The list of RelationshipType objects that are possible - * for the right entity - * @param relationshipType The RelationshipType object that we want to check whether it's - * valid to be added or not - * @return A boolean indicating whether the relationship is left or right. - * Will be set to false if the relationship is valid. - * Will remain unmodified from the left parameter if the - * relationship is not valid. - * @throws SQLException If something goes wrong - */ - private boolean verifyValidRightwardRelationshipType(Context c, Entity entity, - Entity relationEntity, - boolean left, - List - acceptableRelationshipTypes, - List - rightRelationshipTypesForEntity, - RelationshipType relationshipType) - throws SQLException { - if (StringUtils.equalsIgnoreCase(entityService.getType(c, entity).getLabel(), - relationshipType.getRightType().getLabel()) && - StringUtils.equalsIgnoreCase(entityService.getType(c, relationEntity).getLabel(), - relationshipType.getLeftType().getLabel())) { - - for (RelationshipType rightRelationshipType : rightRelationshipTypesForEntity) { - if (StringUtils.equalsIgnoreCase(rightRelationshipType.getLeftType().getLabel(), - relationshipType.getLeftType().getLabel()) || - StringUtils.equalsIgnoreCase(rightRelationshipType.getRightType().getLabel(), - relationshipType.getLeftType().getLabel())) { - left = false; - acceptableRelationshipTypes.add(relationshipType); - } - } - } - return left; - } - - /** - * This method will add RelationshipType objects to the acceptableRelationshipTypes list - * if applicable and valid RelationshipType objects are found. It will also return a boolean indicating - * whether we're dealing with a left Relationship or not - * @param c The relevant DSpace context - * @param entity The Entity for which the RelationshipType has to be checked - * @param relationEntity The other Entity of the Relationship - * @param left Boolean indicating whether the Relationship is left or not - * @param acceptableRelationshipTypes The list of RelationshipType objects that will be added to - * @param leftRelationshipTypesForEntity The list of RelationshipType objects that are possible - * for the left entity - * @param relationshipType The RelationshipType object that we want to check whether it's - * valid to be added or not - * @return A boolean indicating whether the relationship is left or right. - * Will be set to true if the relationship is valid. - * Will remain unmodified from the left parameter if the - * relationship is not valid. - * @throws SQLException If something goes wrong - */ - private boolean verifyValidLeftwardRelationshipType(Context c, Entity entity, - Entity relationEntity, - boolean left, - List - acceptableRelationshipTypes, - List - leftRelationshipTypesForEntity, - RelationshipType relationshipType) - throws SQLException { - if (StringUtils.equalsIgnoreCase(entityService.getType(c, entity).getLabel(), - relationshipType.getLeftType().getLabel()) && - StringUtils.equalsIgnoreCase(entityService.getType(c, relationEntity).getLabel(), - relationshipType.getRightType().getLabel())) { - for (RelationshipType leftRelationshipType : leftRelationshipTypesForEntity) { - if (StringUtils.equalsIgnoreCase(leftRelationshipType.getRightType().getLabel(), - relationshipType.getRightType().getLabel()) || - StringUtils.equalsIgnoreCase(leftRelationshipType.getLeftType().getLabel(), - relationshipType.getRightType().getLabel())) { - left = true; - acceptableRelationshipTypes.add(relationshipType); - } - } - } - return left; - } - - /** - * This method will add the relationship.type metadata to the item if an EntityType can be found for the value in - * the values list. - * @param c The relevant DSpace context - * @param item The item to which this metadatavalue will be added - * @param schema The schema for the metadatavalue to be added - * @param element The element for the metadatavalue to be added - * @param qualifier The qualifier for the metadatavalue to be added - * @param language The language for the metadatavalue to be added - * @param values The value on which we'll search for EntityType object and it's the value - * for the metadatavalue that will be created - * @param authorities The authority for the metadatavalue. This will be filled with the ID - * of the found EntityType for the value if it exists - * @param confidences The confidence for the metadatavalue - * @throws SQLException If something goes wrong - * @throws AuthorizeException If something goes wrong - */ - private void handleRelationTypeMetadata(Context c, Item item, String schema, String element, String qualifier, - String language, List values, List authorities, - List confidences) - throws SQLException, AuthorizeException { - EntityType entityType = entityTypeService.findByEntityType(c, values.get(0)); - if (entityType != null) { - authorities.add(String.valueOf(entityType.getID())); - itemService.clearMetadata(c, item, schema, element, qualifier, language); - itemService.addMetadata(c, item, schema, element, qualifier, language, - values, authorities, confidences); - itemService.update(c, item); - } - } - /** * Compare changes between an items owning collection and mapped collections * and what is in the CSV file @@ -1586,9 +1425,6 @@ public class MetadataImport { // Display the changes displayChanges(changes, true); - - // Commit the change to the DB -// c.commit(); } // Finsh off and tidy up @@ -1603,24 +1439,28 @@ public class MetadataImport { } /** - * Gets a copy of the given csv line with all entity references resolved to UUID strings. + * Gets a copy of the given csv line with all entity target references resolved to UUID strings. * Keys being iterated over represent metadatafields or special columns to be processed. * * @param line the csv line to process. * @return a copy, with all references resolved. - * @throws MetadataImportException if there is an error resolving any entity reference. + * @throws MetadataImportException if there is an error resolving any entity target reference. */ public DSpaceCSVLine resolveEntityRefs(DSpaceCSVLine line) throws MetadataImportException { DSpaceCSVLine newLine = new DSpaceCSVLine(line.getID()); + UUID originId = evaluateOriginId(line.getID()); for (String key : line.keys()) { - // If a key represents a relation field attempt to resolve the reference from the csvRefMap + // If a key represents a relation field attempt to resolve the target reference from the csvRefMap if (key.split("\\.")[0].equalsIgnoreCase("relation")) { if (line.get(key).size() > 0) { for (String val : line.get(key)) { - // Attempt to resolve the relation reference - // These can be a UUID, metadata reference or rowName reference + // Attempt to resolve the relation target reference + // These can be a UUID, metadata target reference or rowName target reference String uuid = resolveEntityRef(c, val).toString(); newLine.add(key, uuid); + //Entity refs have been resolved / placeholdered + //Populate the EntityRelationMap + populateEntityRelationMap(uuid, key, originId.toString()); } } } else { @@ -1635,27 +1475,59 @@ public class MetadataImport { } } } + return newLine; } /** - * Populates the csvRefMap and csvRowMap for the given csv line. + * Populate the entityRelationMap with all target references and it's asscoiated typeNames + * to their respective origins + * + * @param refUUID the target reference UUID for the relation + * @param relationField the field of the typeNames to relate from + */ + private void populateEntityRelationMap(String refUUID, String relationField, String originId) { + HashMap> typeNames = null; + if (entityRelationMap.get(refUUID) == null) { + typeNames = new HashMap<>(); + ArrayList originIds = new ArrayList<>(); + originIds.add(originId); + typeNames.put(relationField, originIds); + entityRelationMap.put(refUUID, typeNames); + } else { + typeNames = entityRelationMap.get(refUUID); + if (typeNames.get(relationField) == null) { + ArrayList originIds = new ArrayList<>(); + originIds.add(originId); + typeNames.put(relationField, originIds); + } else { + ArrayList originIds = typeNames.get(relationField); + originIds.add(originId); + typeNames.put(relationField, originIds); + } + entityRelationMap.put(refUUID, typeNames); + } + } + + /** + * Populates the csvRefMap, csvRowMap, and entityTypeMap for the given csv line. * * The csvRefMap is an index that keeps track of which rows have a specific value for * a specific metadata field or the special "rowName" column. This is used to help resolve indirect - * entity references in the same CSV. + * entity target references in the same CSV. * * The csvRowMap is a row number to UUID map, and contains an entry for every row that has * been processed so far which has a known (minted) UUID for its item. This is used to help complete * the resolution after the row number has been determined. * * @param line the csv line. - * @param rowNumber the row number. * @param uuid the uuid of the item, which may be null if it has not been minted yet. */ - private void populateRefAndRowMap(DSpaceCSVLine line, int rowNumber, @Nullable UUID uuid) { + private void populateRefAndRowMap(DSpaceCSVLine line, @Nullable UUID uuid) { if (uuid != null) { - csvRowMap.put(rowNumber, uuid); + csvRowMap.put(rowCount, uuid); + } else { + csvRowMap.put(rowCount, new UUID(0, rowCount)); } for (String key : line.keys()) { if (key.contains(".") && !key.split("\\.")[0].equalsIgnoreCase("relation") || @@ -1667,22 +1539,31 @@ public class MetadataImport { rowNums = new HashSet<>(); csvRefMap.put(valueKey, rowNums); } - rowNums.add(rowNumber); + rowNums.add(rowCount); + } + } + //Populate entityTypeMap + if (key.equalsIgnoreCase("relationship.type") && line.get(key).size() > 0) { + if (uuid == null) { + entityTypeMap.put(new UUID(0, rowCount), line.get(key).get(0)); + } else { + entityTypeMap.put(uuid, line.get(key).get(0)); } } } } /** - * Gets the UUID of the item indicated by the given reference, which may be a direct UUID string, a row reference + * Gets the UUID of the item indicated by the given target reference, + * which may be a direct UUID string, a row reference * of the form rowName:VALUE, or a metadata value reference of the form schema.element[.qualifier]:VALUE. * * The reference may refer to a previously-processed item in the CSV or an item in the database. * * @param context the context to use. - * @param reference the reference which may be a UUID, metadata reference, or rowName reference. + * @param reference the target reference which may be a UUID, metadata reference, or rowName reference. * @return the uuid. - * @throws MetadataImportException if the reference is malformed or ambiguous (refers to multiple items). + * @throws MetadataImportException if the target reference is malformed or ambiguous (refers to multiple items). */ private UUID resolveEntityRef(Context context, String reference) throws MetadataImportException { // value reference @@ -1692,7 +1573,8 @@ public class MetadataImport { try { return UUID.fromString(reference); } catch (IllegalArgumentException e) { - throw new MetadataImportException("Not a UUID or indirect entity reference: '" + reference + "'"); + throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + + "Not a UUID or indirect entity reference: '" + reference + "'"); } } else if (!reference.startsWith("rowName:") ) { // Not a rowName ref; so it's a metadata value reference MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService(); @@ -1702,7 +1584,8 @@ public class MetadataImport { String mfValue = reference.substring(i + 1); String mf[] = reference.substring(0, i).split("\\."); if (mf.length < 2) { - throw new MetadataImportException("Bad metadata field in reference: '" + reference + throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + + "Bad metadata field in reference: '" + reference + "' (expected syntax is schema.element[.qualifier])"); } String schema = mf[0]; @@ -1715,11 +1598,13 @@ public class MetadataImport { MetadataValue mdvVal = mdv.next(); uuid = mdvVal.getDSpaceObject().getID(); if (mdv.hasNext()) { - throw new MetadataImportException("Ambiguous reference; multiple matches in db: " + reference); + throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + + "Ambiguous reference; multiple matches in db: " + reference); } } } catch (SQLException e) { - throw new MetadataImportException("Error looking up item by metadata reference: " + reference, e); + throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + + "Error looking up item by metadata reference: " + reference, e); } } // Lookup UUIDs that may have already been processed into the csvRefMap @@ -1727,20 +1612,23 @@ public class MetadataImport { // See getMatchingCSVUUIDs() for how the reference param is sourced from the csvRefMap Set csvUUIDs = getMatchingCSVUUIDs(reference); if (csvUUIDs.size() > 1) { - throw new MetadataImportException("Ambiguous reference; multiple matches in csv: " + reference); + throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + + "Ambiguous reference; multiple matches in csv: " + reference); } else if (csvUUIDs.size() == 1) { UUID csvUUID = csvUUIDs.iterator().next(); if (csvUUID.equals(uuid)) { return uuid; // one match from csv and db (same item) } else if (uuid != null) { - throw new MetadataImportException("Ambiguous reference; multiple matches in db and csv: " + reference); + throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + + "Ambiguous reference; multiple matches in db and csv: " + reference); } else { return csvUUID; // one match from csv } } else { // size == 0; the reference does not exist throw an error if (uuid == null) { - throw new MetadataImportException("No matches found for reference: " + reference - + ", Keep in mind you can only reference entries that are listed before " + + throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + + "No matches found for reference: " + reference + + "\nKeep in mind you can only reference entries that are listed before " + "this one within the CSV."); } else { return uuid; // one match from db @@ -1780,4 +1668,187 @@ public class MetadataImport { } } + /** + * Return a UUID of the origin in process or a placeholder for the origin to be evaluated later + * + * @param originId UUID of the origin + * @return the UUID of the item or UUID placeholder + */ + private UUID evaluateOriginId(@Nullable UUID originId) { + if (originId != null) { + return originId; + } else { + return new UUID(0, rowCount); + } + } + + /** + * Validate every relation modification expressed in the CSV. + * + */ + private void validateExpressedRelations() throws MetadataImportException { + for (String targetUUID : entityRelationMap.keySet()) { + String targetType = null; + try { + // Get the type of reference. Attempt lookup in processed map first before looking in archive. + if (entityTypeMap.get(UUID.fromString(targetUUID)) != null) { + targetType = entityTypeService. + findByEntityType(c, entityTypeMap.get(UUID.fromString(targetUUID))).getLabel(); + } else { + // Target item may be archived; check there. + // Add to errors if Realtionship.type cannot be derived + Item targetItem = null; + if (itemService.find(c, UUID.fromString(targetUUID)) != null) { + targetItem = itemService.find(c, UUID.fromString(targetUUID)); + List relTypes = itemService. + getMetadata(targetItem, "relationship", "type", null, Item.ANY); + String relTypeValue = null; + if (relTypes.size() > 0) { + relTypeValue = relTypes.get(0).getValue(); + targetType = entityTypeService.findByEntityType(c, relTypeValue).getLabel(); + } else { + relationValidationErrors.add("Cannot resolve Entity type for target UUID: " + + targetUUID); + } + } else { + relationValidationErrors.add("Cannot resolve Entity type for target UUID: " + + targetUUID); + } + } + if (targetType == null) { + continue; + } + // Get typeNames for each origin referer of this target. + for (String typeName : entityRelationMap.get(targetUUID).keySet()) { + // Resolve Entity Type for each origin referer. + for (String originRefererUUID : entityRelationMap.get(targetUUID).get(typeName)) { + // Evaluate row number for origin referer. + String originRow = "N/A"; + if (csvRowMap.containsValue(UUID.fromString(originRefererUUID))) { + for (int key : csvRowMap.keySet()) { + if (csvRowMap.get(key).toString().equalsIgnoreCase(originRefererUUID)) { + originRow = key + ""; + break; + } + } + } + String originType = ""; + // Validate target type and origin type pairing with typeName or add to errors. + // Attempt lookup in processed map first before looking in archive. + if (entityTypeMap.get(UUID.fromString(originRefererUUID)) != null) { + originType = entityTypeMap.get(UUID.fromString(originRefererUUID)); + validateTypesByTypeByTypeName(targetType, originType, typeName, originRow); + } else { + // Origin item may be archived; check there. + // Add to errors if Realtionship.type cannot be derived. + Item originItem = null; + if (itemService.find(c, UUID.fromString(targetUUID)) != null) { + originItem = itemService.find(c, UUID.fromString(originRefererUUID)); + List relTypes = itemService. + getMetadata(originItem, "relationship", "type", null, Item.ANY); + String relTypeValue = null; + if (relTypes.size() > 0) { + relTypeValue = relTypes.get(0).getValue(); + originType = entityTypeService.findByEntityType(c, relTypeValue).getLabel(); + validateTypesByTypeByTypeName(targetType, originType, typeName, originRow); + } else { + relationValidationErrors.add("Error on CSV row " + originRow + ":" + "\n" + + "Cannot resolve Entity type for reference: " + + originRefererUUID); + } + + } else { + relationValidationErrors.add("Error on CSV row " + originRow + ":" + "\n" + + "Cannot resolve Entity type for reference: " + + originRefererUUID + " in row: " + originRow ); + } + } + } + } + + } catch (SQLException sqle) { + throw new MetadataImportException("Error interacting with database!", sqle); + } + + } // If relationValidationErrors is empty all described relationships are valid. + if (!relationValidationErrors.isEmpty()) { + StringBuilder errors = new StringBuilder(); + for (String error : relationValidationErrors) { + errors.append(error + "\n"); + } + throw new MetadataImportException("Error validating relationships: \n" + errors); + } + } + + /** + * Generates a list of potenital Relationship Types given a typeName and attempts to match the given + * targetType and originType to a Relationship Type in the list. + * + * @param targetType entity type of target. + * @param originType entity type of origin referer. + * @param typeName left or right typeName of the respective Relationship. + * @return the UUID of the item. + */ + private void validateTypesByTypeByTypeName(String targetType, String originType, String typeName, String originRow) + throws MetadataImportException { + try { + RelationshipType foundRelationshipType = null; + List relationshipTypeList = relationshipTypeService. + findByLeftwardOrRightwardTypeName(c, typeName.split("\\.")[1]); + // Validate described relationship form the CSV. + foundRelationshipType = matchRelationshipType(relationshipTypeList, targetType, originType, typeName); + if (foundRelationshipType == null) { + relationValidationErrors.add("Error on CSV row " + originRow + ":" + "\n" + + "No Relationship type found for:\n" + + "Target type: " + targetType + "\n" + + "Origin referer type: " + originType + "\n" + + "with typeName: " + typeName + " for type: " + originType); + } + } catch (SQLException sqle) { + throw new MetadataImportException("Error interacting with database!", sqle); + } + } + + /** + * Matches two Entity types to a Relationship Type from a set of Relationship Types. + * + * @param relTypes set of Relationship Types. + * @param targetType entity type of target. + * @param originType entity type of origin referer. + * @return null or matched Relationship Type. + */ + private RelationshipType matchRelationshipType(List relTypes, + String targetType, String originType, String originTypeName) { + RelationshipType foundRelationshipType = null; + if (originTypeName.split("\\.").length > 1) { + originTypeName = originTypeName.split("\\.")[1]; + } + for (RelationshipType relationshipType : relTypes) { + // Is origin type leftward or righward + boolean isLeft = false; + if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(originType)) { + isLeft = true; + } + if (isLeft) { + // Validate typeName reference + if (!relationshipType.getLeftwardType().equalsIgnoreCase(originTypeName)) { + continue; + } + if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(originType) && + relationshipType.getRightType().getLabel().equalsIgnoreCase(targetType)) { + foundRelationshipType = relationshipType; + } + } else { + if (!relationshipType.getRightwardType().equalsIgnoreCase(originTypeName)) { + continue; + } + if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(targetType) && + relationshipType.getRightType().getLabel().equalsIgnoreCase(originType)) { + foundRelationshipType = relationshipType; + } + } + } + return foundRelationshipType; + } + } diff --git a/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java b/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java index 7fa37e2cef..ce3567e916 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SearchUtils.java @@ -10,9 +10,9 @@ package org.dspace.discovery; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; +import java.util.HashSet; import java.util.List; -import java.util.Map; +import java.util.Set; import org.dspace.content.Collection; import org.dspace.content.DSpaceObject; @@ -140,26 +140,26 @@ public class SearchUtils { private static List getAllDiscoveryConfigurations(String prefix, List collections, Item item) throws SQLException { - Map result = new HashMap(); + Set result = new HashSet<>(); for (Collection collection : collections) { DiscoveryConfiguration configuration = getDiscoveryConfiguration(prefix, collection); - if (!result.containsKey(configuration.getId())) { - result.put(configuration.getId(), configuration); - } + result.add(configuration); } + //Add alwaysIndex configurations + DiscoveryConfigurationService configurationService = getConfigurationService(); + result.addAll(configurationService.getIndexAlwaysConfigurations()); + //Also add one for the default addConfigurationIfExists(result, prefix); - return Arrays.asList(result.values().toArray(new DiscoveryConfiguration[result.size()])); + return Arrays.asList(result.toArray(new DiscoveryConfiguration[result.size()])); } - private static void addConfigurationIfExists(Map result, String confName) { + private static void addConfigurationIfExists(Set result, String confName) { DiscoveryConfiguration configurationExtra = getDiscoveryConfigurationByName(confName); - if (!result.containsKey(configurationExtra.getId())) { - result.put(configurationExtra.getId(), configurationExtra); - } + result.add(configurationExtra); } } diff --git a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfiguration.java b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfiguration.java index b9fe50a7ae..9ff6f9941b 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfiguration.java @@ -51,6 +51,22 @@ public class DiscoveryConfiguration implements InitializingBean { private DiscoveryHitHighlightingConfiguration hitHighlightingConfiguration; private DiscoveryMoreLikeThisConfiguration moreLikeThisConfiguration; private boolean spellCheckEnabled; + private boolean indexAlways = false; + + /** + * The `indexAlways` property determines whether the configuration should always be included when indexing items. + * The default value is false which implies the configuration is only used when it matches the collection or if + * it's the default configuration + * When set to true, the configuration is also used to index an item without a specific collection mapping + * This can be used for displaying different facets depending on the type of item instead of the collection + */ + public boolean isIndexAlways() { + return indexAlways; + } + + public void setIndexAlways(boolean indexAlways) { + this.indexAlways = indexAlways; + } public String getId() { return id; diff --git a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java index f79596fe1b..53433f879f 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java +++ b/dspace-api/src/main/java/org/dspace/discovery/configuration/DiscoveryConfigurationService.java @@ -7,6 +7,7 @@ */ package org.dspace.discovery.configuration; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -75,6 +76,22 @@ public class DiscoveryConfigurationService { } } + /** + * Retrieves a list of all DiscoveryConfiguration objects where + * {@link org.dspace.discovery.configuration.DiscoveryConfiguration#isIndexAlways()} is true + * These configurations should always be included when indexing + */ + public List getIndexAlwaysConfigurations() { + List configs = new ArrayList<>(); + for (String key : map.keySet()) { + DiscoveryConfiguration config = map.get(key); + if (config.isIndexAlways()) { + configs.add(config); + } + } + return configs; + } + public static void main(String[] args) { System.out.println(DSpaceServicesFactory.getInstance().getServiceManager().getServicesNames().size()); DiscoveryConfigurationService mainService = DSpaceServicesFactory.getInstance().getServiceManager() diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CSVMetadataImportReferenceTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CSVMetadataImportReferenceTest.java index acad5981f6..57a0475b63 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CSVMetadataImportReferenceTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CSVMetadataImportReferenceTest.java @@ -33,7 +33,6 @@ import org.dspace.content.service.ItemService; import org.dspace.content.service.MetadataFieldService; import org.dspace.content.service.MetadataValueService; import org.dspace.content.service.RelationshipService; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -63,13 +62,6 @@ public class CSVMetadataImportReferenceTest extends AbstractEntityIntegrationTes .withName("Parent Community") .build(); col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); - } - - /** - * Close testing enviorment and restore auth system - */ - @After - public void close() { context.restoreAuthSystemState(); } @@ -180,9 +172,11 @@ public class CSVMetadataImportReferenceTest extends AbstractEntityIntegrationTes */ @Test public void testSingleUUIDReference() throws Exception { + context.turnOffAuthorisationSystem(); Item person = ItemBuilder.createItem(context, col1) .withRelationshipType("Person") .build(); + context.restoreAuthSystemState(); String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,rowName,dc.identifier.other", "+,Publication," + person.getID().toString() + "," + col1.getHandle() + ",anything,0"}; Item[] items = runImport(csv); @@ -195,12 +189,14 @@ public class CSVMetadataImportReferenceTest extends AbstractEntityIntegrationTes */ @Test public void testMultiUUIDReference() throws Exception { + context.turnOffAuthorisationSystem(); Item person = ItemBuilder.createItem(context, col1) .withRelationshipType("Person") .build(); Item person2 = ItemBuilder.createItem(context, col1) .withRelationshipType("Person") .build(); + context.restoreAuthSystemState(); String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,rowName,dc.identifier.other", "+,Publication," + person.getID().toString() + "||" + person2.getID().toString() + "," + col1.getHandle() + ",anything,0"}; @@ -215,6 +211,7 @@ public class CSVMetadataImportReferenceTest extends AbstractEntityIntegrationTes */ @Test public void testMultiRefArchivedCsv() throws Exception { + context.turnOffAuthorisationSystem(); Item person = ItemBuilder.createItem(context, col1) .withTitle("Person") .withRelationshipType("Person") @@ -223,6 +220,7 @@ public class CSVMetadataImportReferenceTest extends AbstractEntityIntegrationTes "dc.identifier.other", "+,Person2,Person,," + col1.getHandle() + ",idVal,0", "+,Pub1,Publication,dc.title:Person||dc.title:Person2," + col1.getHandle() + ",anything,1"}; + context.restoreAuthSystemState(); Item[] items = runImport(csv); assertRelationship(items[1], person, 1, "left", 0); assertRelationship(items[1], items[0], 1, "left", 1); @@ -235,6 +233,7 @@ public class CSVMetadataImportReferenceTest extends AbstractEntityIntegrationTes */ @Test public void testMultiMixedRefArchivedCsv() throws Exception { + context.turnOffAuthorisationSystem(); Item person = ItemBuilder.createItem(context, col1) .withTitle("Person") .withRelationshipType("Person") @@ -243,6 +242,7 @@ public class CSVMetadataImportReferenceTest extends AbstractEntityIntegrationTes .withTitle("Person2") .withRelationshipType("Person") .build(); + context.restoreAuthSystemState(); String[] csv = {"id,dc.title,relationship.type,relation.isAuthorOfPublication,collection,rowName," + "dc.identifier.other", "+,Person3,Person,," + col1.getHandle() + ",idVal,0", @@ -297,6 +297,7 @@ public class CSVMetadataImportReferenceTest extends AbstractEntityIntegrationTes */ @Test public void testNonUniqueMDRefInDb() throws Exception { + context.turnOffAuthorisationSystem(); ItemBuilder.createItem(context, col1) .withRelationshipType("Person") .withIdentifierOther("1") @@ -305,6 +306,7 @@ public class CSVMetadataImportReferenceTest extends AbstractEntityIntegrationTes .withRelationshipType("Person") .withIdentifierOther("1") .build(); + context.restoreAuthSystemState(); String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,dc.identifier.other", "+,Publication,dc.identifier.other:1," + col1.getHandle() + ",2"}; assertEquals(1, performImportScript(csv, true)); @@ -315,10 +317,12 @@ public class CSVMetadataImportReferenceTest extends AbstractEntityIntegrationTes */ @Test public void testNonUniqueMDRefInBoth() throws Exception { + context.turnOffAuthorisationSystem(); ItemBuilder.createItem(context, col1) .withRelationshipType("Person") .withIdentifierOther("1") .build(); + context.restoreAuthSystemState(); String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,dc.identifier.other", "+,Person,," + col1.getHandle() + ",1", "+,Publication,dc.identifier.other:1," + col1.getHandle() + ",2"}; @@ -358,6 +362,107 @@ public class CSVMetadataImportReferenceTest extends AbstractEntityIntegrationTes assertEquals(1, performImportScript(csv, false)); } + /** + * Test relationship validation with invalid relationship definition + */ + @Test + public void testCSVImportInvalidRelationship() throws Exception { + String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,rowName", + "+,Publication,," + col1.getHandle() + ",row1", + "+,Unit,rowName:row1," + col1.getHandle() + ",row2",}; + assertEquals(1, performImportScript(csv, true)); + } + + /** + * Test relationship validation with invalid relationship definition and with an archived origin referer + */ + @Test + public void testInvalidRelationshipArchivedOrigin() throws Exception { + context.turnOffAuthorisationSystem(); + Item testItem = ItemBuilder.createItem(context, col1) + .withRelationshipType("OrgUnit") + .build(); + context.restoreAuthSystemState(); + String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,rowName", + "+,Person,," + col1.getHandle() + ",1" + + testItem.getID().toString() + ",,rowName:1," + col1.getHandle() + ",2"}; + assertEquals(1, performImportScript(csv, false)); + } + + /** + * Test relationship validation with invalid relationship definition and with archived target reference + */ + @Test + public void testInvalidRelationshipArchivedTarget() throws Exception { + context.turnOffAuthorisationSystem(); + Item testItem = ItemBuilder.createItem(context, col1) + .withRelationshipType("OrgUnit") + .build(); + context.restoreAuthSystemState(); + String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,rowName", + testItem.getID().toString() + ",Person,," + col1.getHandle() + ",1" + + "+,OrgUnit,rowName:1," + col1.getHandle() + ",2"}; + assertEquals(1, performImportScript(csv, false)); + } + + /** + * Test relationship validation against archived items with pre-existing relationship types + */ + @Test + public void testValidRelationshipNoDefinedTypesInCSV() throws Exception { + context.turnOffAuthorisationSystem(); + Item testItemOne = ItemBuilder.createItem(context, col1) + .withRelationshipType("Person") + .withIdentifierOther("testItemOne") + .build(); + Item testItemTwo = ItemBuilder.createItem(context, col1) + .withRelationshipType("Publication") + .withIdentifierOther("testItemTwo") + .build(); + Item testItemThree = ItemBuilder.createItem(context, col1) + .withRelationshipType("Project") + .withIdentifierOther("testItemThree") + .build(); + context.restoreAuthSystemState(); + String[] csv = {"id,relation.isAuthorOfPublication,relation.isPublicationOfProject,collection", + testItemOne.getID().toString() + ",,," + col1.getHandle(), + testItemTwo.getID().toString() + ",dc.identifier.other:testItemOne,," + col1.getHandle(), + testItemThree.getID().toString() + ",,dc.identifier.other:testItemTwo," + col1.getHandle()}; + performImportScript(csv, false); + assertRelationship(testItemTwo, testItemOne, 1, "left", 0); + assertRelationship(testItemTwo, testItemThree, 1, "left", 0); + } + + /** + * Test relationship validation with valid relationship definition using the same rowName more than once + */ + @Test + public void testDuplicateRowNameReferences() throws Exception { + String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,dc.identifier.other,rowName", + "+,Person,," + col1.getHandle() + ",0,value", + "+,Publication,rowName:value," + col1.getHandle() + ",1,1", + "+,Publication,rowName:value," + col1.getHandle() + ",2,2"}; + Item[] items = runImport(csv); + assertRelationship(items[1], items[0], 1, "left", 0); + assertRelationship(items[2], items[0], 1, "left", 0); + } + + /** + * Test relationship validation with invalid relationship definition by incorrect typeName usage + */ + @Test + public void testInvalidTypeNameDefined() throws Exception { + context.turnOffAuthorisationSystem(); + Item testItem = ItemBuilder.createItem(context, col1) + .withRelationshipType("Publication") + .build(); + context.restoreAuthSystemState(); + String[] csv = {"id,collection,relationship.type,dc.title," + + "relation.isProjectOfPublication,relation.isPublicationOfProject", + "+," + col1.getHandle() + ",Project,Title," + + testItem.getID().toString() + "," + testItem.getID().toString() }; + assertEquals(1, performImportScript(csv, true)); + } /** * Import mocked CSVs to test item creation behavior, deleting temporary file afterward. diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 4bb23f5db3..3fca7ee731 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -26,7 +26,7 @@ dspace.dir = /dspace dspace.hostname = localhost # DSpace base host URL. Include port number etc. -dspace.baseUrl = http://localhost:8080/server +dspace.baseUrl = http://localhost:8080 # Full link your end users will use to access DSpace. In most cases, this will be the baseurl followed by # the context path to the UI you are using. @@ -37,7 +37,7 @@ dspace.url = ${dspace.baseUrl} # This is the URL that will be used for the REST endpoints to be served on. # This will typically be followed by /api to determine the root endpoints. -dspace.restUrl = ${dspace.baseUrl} +dspace.restUrl = ${dspace.baseUrl}/server # Optional: DSpace URL for mobile access # This diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index 32ab9d150c..ad985b0d9c 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -516,6 +516,7 @@ + @@ -586,6 +587,7 @@ + @@ -649,6 +651,7 @@ + @@ -713,6 +716,7 @@ + @@ -773,6 +777,7 @@ + @@ -832,6 +837,7 @@ +