diff --git a/Dockerfile.jdk8 b/Dockerfile.jdk8 index afe2285f49..b5c108d129 100644 --- a/Dockerfile.jdk8 +++ b/Dockerfile.jdk8 @@ -54,10 +54,7 @@ EXPOSE 8080 8009 ENV JAVA_OPTS=-Xmx2000m -RUN ln -s $DSPACE_INSTALL/webapps/solr /usr/local/tomcat/webapps/solr && \ +RUN mv /usr/local/tomcat/webapps/ROOT /usr/local/tomcat/webapps/ROOT.bk && \ + ln -s $DSPACE_INSTALL/webapps/spring-rest /usr/local/tomcat/webapps/ROOT && \ ln -s $DSPACE_INSTALL/webapps/spring-rest /usr/local/tomcat/webapps/spring-rest && \ - ln -s $DSPACE_INSTALL/webapps/rest /usr/local/tomcat/webapps/rest && \ - ln -s $DSPACE_INSTALL/webapps/oai /usr/local/tomcat/webapps/oai && \ - ln -s $DSPACE_INSTALL/webapps/rdf /usr/local/tomcat/webapps/rdf && \ - ln -s $DSPACE_INSTALL/webapps/sword /usr/local/tomcat/webapps/sword && \ - ln -s $DSPACE_INSTALL/webapps/swordv2 /usr/local/tomcat/webapps/swordv2 + ln -s $DSPACE_INSTALL/webapps/rest /usr/local/tomcat/webapps/rest diff --git a/Dockerfile.jdk8-test b/Dockerfile.jdk8-test index 01697a045c..54ef4d105f 100644 --- a/Dockerfile.jdk8-test +++ b/Dockerfile.jdk8-test @@ -54,16 +54,11 @@ EXPOSE 8080 8009 ENV JAVA_OPTS=-Xmx2000m -RUN ln -s $DSPACE_INSTALL/webapps/solr /usr/local/tomcat/webapps/solr && \ +RUN mv /usr/local/tomcat/webapps/ROOT /usr/local/tomcat/webapps/ROOT.bk && \ + ln -s $DSPACE_INSTALL/webapps/spring-rest /usr/local/tomcat/webapps/ROOT && \ ln -s $DSPACE_INSTALL/webapps/spring-rest /usr/local/tomcat/webapps/spring-rest && \ - ln -s $DSPACE_INSTALL/webapps/rest /usr/local/tomcat/webapps/rest && \ - ln -s $DSPACE_INSTALL/webapps/oai /usr/local/tomcat/webapps/oai && \ - ln -s $DSPACE_INSTALL/webapps/rdf /usr/local/tomcat/webapps/rdf && \ - ln -s $DSPACE_INSTALL/webapps/sword /usr/local/tomcat/webapps/sword && \ - ln -s $DSPACE_INSTALL/webapps/swordv2 /usr/local/tomcat/webapps/swordv2 + ln -s $DSPACE_INSTALL/webapps/rest /usr/local/tomcat/webapps/rest -COPY dspace/src/main/docker/test/solr_web.xml $DSPACE_INSTALL/webapps/solr/WEB-INF/web.xml COPY dspace/src/main/docker/test/rest_web.xml $DSPACE_INSTALL/webapps/rest/WEB-INF/web.xml -RUN sed -i -e "s|\${dspace.dir}|$DSPACE_INSTALL|" $DSPACE_INSTALL/webapps/solr/WEB-INF/web.xml && \ - sed -i -e "s|\${dspace.dir}|$DSPACE_INSTALL|" $DSPACE_INSTALL/webapps/rest/WEB-INF/web.xml +RUN sed -i -e "s|\${dspace.dir}|$DSPACE_INSTALL|" $DSPACE_INSTALL/webapps/rest/WEB-INF/web.xml diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 1a7326fa75..4298cfd21e 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -1,5 +1,4 @@ - + 4.0.0 org.dspace dspace-api diff --git a/dspace-api/src/main/java/org/dspace/administer/MetadataImporter.java b/dspace-api/src/main/java/org/dspace/administer/MetadataImporter.java index ac547f1839..aee48f71e8 100644 --- a/dspace-api/src/main/java/org/dspace/administer/MetadataImporter.java +++ b/dspace-api/src/main/java/org/dspace/administer/MetadataImporter.java @@ -21,6 +21,7 @@ import org.apache.xpath.XPathAPI; import org.dspace.authorize.AuthorizeException; import org.dspace.content.MetadataField; import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.NonUniqueMetadataException; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.MetadataFieldService; @@ -248,7 +249,7 @@ public class MetadataImporter { // If the schema is not provided default to DC if (schema == null) { - schema = MetadataSchema.DC_SCHEMA; + schema = MetadataSchemaEnum.DC.getName(); } diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java index e66c96782b..ea95be7e72 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java @@ -27,6 +27,7 @@ import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.commons.lang3.StringUtils; import org.dspace.authority.AuthorityValue; import org.dspace.authority.factory.AuthorityServiceFactory; import org.dspace.authority.service.AuthorityValueService; @@ -34,6 +35,7 @@ import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.content.MetadataField; import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.MetadataValue; import org.dspace.content.authority.Choices; import org.dspace.content.factory.ContentServiceFactory; @@ -198,20 +200,24 @@ public class DSpaceCSV implements Serializable { } // Check that the scheme exists - MetadataSchema foundSchema = metadataSchemaService.find(c, metadataSchema); - if (foundSchema == null) { - throw new MetadataImportInvalidHeadingException(clean[0], - MetadataImportInvalidHeadingException.SCHEMA, - columnCounter); - } + if (!StringUtils.equals(metadataSchema, MetadataSchemaEnum.RELATION.getName())) { + MetadataSchema foundSchema = metadataSchemaService.find(c, metadataSchema); + if (foundSchema == null) { + throw new MetadataImportInvalidHeadingException(clean[0], + MetadataImportInvalidHeadingException + .SCHEMA, + columnCounter); + } - // Check that the metadata element exists in the schema - MetadataField foundField = metadataFieldService - .findByElement(c, foundSchema, metadataElement, metadataQualifier); - if (foundField == null) { - throw new MetadataImportInvalidHeadingException(clean[0], - MetadataImportInvalidHeadingException.ELEMENT, - columnCounter); + // Check that the metadata element exists in the schema + MetadataField foundField = metadataFieldService + .findByElement(c, foundSchema, metadataElement, metadataQualifier); + if (foundField == null) { + throw new MetadataImportInvalidHeadingException(clean[0], + MetadataImportInvalidHeadingException + .ELEMENT, + columnCounter); + } } // Store the heading 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 b8b7b7311f..e1b1224809 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 @@ -15,6 +15,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.UUID; @@ -33,14 +34,23 @@ import org.dspace.authority.service.AuthorityValueService; 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.MetadataSchemaEnum; import org.dspace.content.MetadataValue; +import org.dspace.content.Relationship; +import org.dspace.content.RelationshipType; import org.dspace.content.WorkspaceItem; import org.dspace.content.authority.Choices; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CollectionService; +import org.dspace.content.service.EntityService; +import org.dspace.content.service.EntityTypeService; import org.dspace.content.service.InstallItemService; import org.dspace.content.service.ItemService; +import org.dspace.content.service.RelationshipService; +import org.dspace.content.service.RelationshipTypeService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.ConfigurationManager; import org.dspace.core.Constants; @@ -50,6 +60,7 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.handle.service.HandleService; +import org.dspace.util.UUIDUtils; import org.dspace.workflow.WorkflowItem; import org.dspace.workflow.WorkflowService; import org.dspace.workflow.factory.WorkflowServiceFactory; @@ -101,6 +112,10 @@ public class MetadataImport { protected final CollectionService collectionService; protected final HandleService handleService; protected final WorkspaceItemService workspaceItemService; + protected final RelationshipTypeService relationshipTypeService; + protected final RelationshipService relationshipService; + protected final EntityTypeService entityTypeService; + protected final EntityService entityService; /** * Create an instance of the metadata importer. Requires a context and an array of CSV lines @@ -120,6 +135,10 @@ public class MetadataImport { handleService = HandleServiceFactory.getInstance().getHandleService(); authorityValueService = AuthorityServiceFactory.getInstance().getAuthorityValueService(); workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); + relationshipService = ContentServiceFactory.getInstance().getRelationshipService(); + relationshipTypeService = ContentServiceFactory.getInstance().getRelationshipTypeService(); + entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService(); + entityService = ContentServiceFactory.getInstance().getEntityService(); } /** @@ -336,16 +355,30 @@ 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()); + 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()); + } } + for (BulkEditMetadataValue relationship : relationships) { + handleRelationshipMetadataValueFromBulkEditMetadataValue(item, relationship); + } // Should the workflow be used? if (useWorkflow) { WorkflowService workflowService = WorkflowServiceFactory.getInstance().getWorkflowService(); @@ -396,6 +429,27 @@ public class MetadataImport { 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 { + 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 * @@ -583,9 +637,251 @@ public class MetadataImport { } } - // Set those values + + if (StringUtils.equals(schema, MetadataSchemaEnum.RELATION.getName())) { + List relationshipTypeList = relationshipTypeService + .findByLeftOrRightLabel(c, element); + for (RelationshipType relationshipType : relationshipTypeList) { + for (Relationship relationship : relationshipService + .findByItemAndRelationshipType(c, item, relationshipType)) { + relationshipService.delete(c, relationship); + relationshipService.update(c, relationship); + } + } + handleRelationMetadata(c, item, schema, element, qualifier, language, values, authorities, confidences); + } else { + itemService.clearMetadata(c, item, schema, element, qualifier, language); + itemService.addMetadata(c, item, schema, element, qualifier, + language, values, authorities, confidences); + itemService.update(c, item); + } + } + } + + /** + * 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 + * @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 + * @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 { + + 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); + } + } + + } + + /** + * 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 + * @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 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) + throws SQLException, AuthorizeException { + Entity entity = entityService.findByItemId(c, item.getID()); + boolean left = false; + List acceptableRelationshipTypes = new LinkedList<>(); + String url = handleService.resolveToURL(c, value); + UUID uuid = UUIDUtils.fromString(value); + if (uuid == null && StringUtils.isNotBlank(url)) { + return; + } + + Entity relationEntity = entityService.findByItemId(c, uuid); + + + List leftRelationshipTypesForEntity = entityService.getLeftRelationshipTypes(c, entity); + List rightRelationshipTypesForEntity = entityService.getRightRelationshipTypes(c, entity); + + for (RelationshipType relationshipType : entityService.getAllRelationshipTypes(c, entity)) { + if (StringUtils.equalsIgnoreCase(relationshipType.getLeftLabel(), element)) { + left = handleLeftLabelEqualityRelationshipTypeElement(c, entity, relationEntity, left, + acceptableRelationshipTypes, + leftRelationshipTypesForEntity, + relationshipType); + } else if (StringUtils.equalsIgnoreCase(relationshipType.getRightLabel(), element)) { + left = handleRightLabelEqualityRelationshipTypeElement(c, entity, relationEntity, left, + acceptableRelationshipTypes, + rightRelationshipTypesForEntity, + relationshipType); + } + } + + if (acceptableRelationshipTypes.size() > 1) { + log.error("Ambiguous relationship_types were found"); + return; + } + if (acceptableRelationshipTypes.size() == 0) { + log.error("no relationship_types were found"); + return; + } + + //There is exactly one + buildRelationObject(c, item, value, 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 value The value 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, String value, boolean left, + RelationshipType acceptedRelationshipType) + throws SQLException, AuthorizeException { + Item leftItem = null; + Item rightItem = null; + if (left) { + leftItem = item; + rightItem = itemService.findByIdOrLegacyId(c, value); + } else { + rightItem = item; + leftItem = itemService.findByIdOrLegacyId(c, value); + } + RelationshipType relationshipType = acceptedRelationshipType; + int leftPlace = relationshipService.findLeftPlaceByLeftItem(c, leftItem) + 1; + int rightPlace = relationshipService.findRightPlaceByRightItem(c, rightItem) + 1; + Relationship persistedRelationship = relationshipService.create(c, leftItem, rightItem, + relationshipType, 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 false in this case + * @throws SQLException If something goes wrong + */ + private boolean handleRightLabelEqualityRelationshipTypeElement(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 true in this case + * @throws SQLException If something goes wrong + */ + private boolean handleLeftLabelEqualityRelationshipTypeElement(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.addMetadata(c, item, schema, element, qualifier, language, + values, authorities, confidences); itemService.update(c, item); } } diff --git a/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java index d3ffb14a9a..73bd16c7d7 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java @@ -42,7 +42,7 @@ import org.dspace.content.Community; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.MetadataField; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.MetadataValue; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.CommunityService; @@ -214,7 +214,7 @@ public class ItemExportServiceImpl implements ItemExportService { protected void writeMetadata(Context c, String schema, Item i, File destDir, boolean migrate) throws Exception { String filename; - if (schema.equals(MetadataSchema.DC_SCHEMA)) { + if (schema.equals(MetadataSchemaEnum.DC.getName())) { filename = "dublin_core.xml"; } else { filename = "metadata_" + schema + ".xml"; diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java index e848c7b9a3..1083876d69 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java @@ -74,6 +74,7 @@ import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.MetadataField; import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.WorkspaceItem; import org.dspace.content.service.BitstreamFormatService; import org.dspace.content.service.BitstreamService; @@ -677,7 +678,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea Node schemaAttr = metadata.item(0).getAttributes().getNamedItem( "schema"); if (schemaAttr == null) { - schema = MetadataSchema.DC_SCHEMA; + schema = MetadataSchemaEnum.DC.getName(); } else { schema = schemaAttr.getNodeValue(); } diff --git a/dspace-api/src/main/java/org/dspace/app/itemupdate/MetadataUtilities.java b/dspace-api/src/main/java/org/dspace/app/itemupdate/MetadataUtilities.java index 8b3907ce50..b51212ea2f 100644 --- a/dspace-api/src/main/java/org/dspace/app/itemupdate/MetadataUtilities.java +++ b/dspace-api/src/main/java/org/dspace/app/itemupdate/MetadataUtilities.java @@ -34,6 +34,7 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; import org.dspace.content.MetadataField; import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.MetadataValue; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.ItemService; @@ -189,7 +190,7 @@ public class MetadataUtilities { NodeList metadata = XPathAPI.selectNodeList(document, "/dublin_core"); Node schemaAttr = metadata.item(0).getAttributes().getNamedItem("schema"); if (schemaAttr == null) { - schema = MetadataSchema.DC_SCHEMA; + schema = MetadataSchemaEnum.DC.getName(); } else { schema = schemaAttr.getNodeValue(); } diff --git a/dspace-api/src/main/java/org/dspace/app/statistics/ReportGenerator.java b/dspace-api/src/main/java/org/dspace/app/statistics/ReportGenerator.java index 8c38cc842d..7e9b438547 100644 --- a/dspace-api/src/main/java/org/dspace/app/statistics/ReportGenerator.java +++ b/dspace-api/src/main/java/org/dspace/app/statistics/ReportGenerator.java @@ -28,7 +28,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import org.dspace.content.Item; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.MetadataValue; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.ItemService; @@ -763,9 +763,10 @@ public class ReportGenerator { // build the referece // FIXME: here we have blurred the line between content and presentation // and it should probably be un-blurred - List title = itemService.getMetadata(item, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY); + List title = itemService.getMetadata(item, MetadataSchemaEnum.DC.getName(), + "title", null, Item.ANY); List author = itemService - .getMetadata(item, MetadataSchema.DC_SCHEMA, "contributor", "author", Item.ANY); + .getMetadata(item, MetadataSchemaEnum.DC.getName(), "contributor", "author", Item.ANY); StringBuffer authors = new StringBuffer(); if (author.size() > 0) { diff --git a/dspace-api/src/main/java/org/dspace/app/util/DCInput.java b/dspace-api/src/main/java/org/dspace/app/util/DCInput.java index 58bdb75878..fee32293b2 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/DCInput.java +++ b/dspace-api/src/main/java/org/dspace/app/util/DCInput.java @@ -14,7 +14,7 @@ import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import org.apache.commons.lang3.StringUtils; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.core.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -160,7 +160,7 @@ public class DCInput { // Default the schema to dublin core dcSchema = fieldMap.get("dc-schema"); if (dcSchema == null) { - dcSchema = MetadataSchema.DC_SCHEMA; + dcSchema = MetadataSchemaEnum.DC.getName(); } //check if the input have a language tag diff --git a/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java b/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java index b473e602b8..736458116e 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java +++ b/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java @@ -21,7 +21,7 @@ import javax.xml.parsers.FactoryConfigurationError; import org.apache.commons.lang3.StringUtils; import org.dspace.content.Collection; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.core.Utils; import org.dspace.services.factory.DSpaceServicesFactory; import org.w3c.dom.Document; @@ -464,7 +464,7 @@ public class DCInputsReader { String elem = field.get("dc-element"); String qual = field.get("dc-qualifier"); if ((schema == null) || (schema.equals(""))) { - schema = MetadataSchema.DC_SCHEMA; + schema = MetadataSchemaEnum.DC.getName(); } String schemaTest; @@ -474,7 +474,7 @@ public class DCInputsReader { Map fld = pg.get(j); if ((fld.get("dc-schema") == null) || ((fld.get("dc-schema")).equals(""))) { - schemaTest = MetadataSchema.DC_SCHEMA; + schemaTest = MetadataSchemaEnum.DC.getName(); } else { schemaTest = fld.get("dc-schema"); } diff --git a/dspace-api/src/main/java/org/dspace/app/util/InitializeEntities.java b/dspace-api/src/main/java/org/dspace/app/util/InitializeEntities.java new file mode 100644 index 0000000000..52d61ee17a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/util/InitializeEntities.java @@ -0,0 +1,227 @@ +/** + * 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.util; + +import java.io.File; +import java.io.IOException; +import java.sql.SQLException; +import java.util.LinkedList; +import java.util.List; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.cli.PosixParser; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.EntityType; +import org.dspace.content.RelationshipType; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.EntityTypeService; +import org.dspace.content.service.RelationshipService; +import org.dspace.content.service.RelationshipTypeService; +import org.dspace.core.Context; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +/** + * This script is used to initialize the database with a set of relationshiptypes that are written + * in an xml file that is given to this script. + * This XML file needs to have a proper XML structure and needs to define the variables of the RelationshipType object + */ +public class InitializeEntities { + + private final static Logger log = LogManager.getLogger(); + + private RelationshipTypeService relationshipTypeService; + private RelationshipService relationshipService; + private EntityTypeService entityTypeService; + + private InitializeEntities() { + relationshipTypeService = ContentServiceFactory.getInstance().getRelationshipTypeService(); + relationshipService = ContentServiceFactory.getInstance().getRelationshipService(); + entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService(); + + } + + /** + * The main method for this script + * + * @param argv The commandline arguments given with this command + * @throws SQLException If something goes wrong with the database + * @throws AuthorizeException If something goes wrong with permissions + * @throws ParseException If something goes wrong with the parsing + */ + public static void main(String[] argv) throws SQLException, AuthorizeException, ParseException { + InitializeEntities initializeEntities = new InitializeEntities(); + CommandLineParser parser = new PosixParser(); + Options options = createCommandLineOptions(); + CommandLine line = parser.parse(options,argv); + String fileLocation = getFileLocationFromCommandLine(line); + checkHelpEntered(options, line); + initializeEntities.run(fileLocation); + } + private static void checkHelpEntered(Options options, CommandLine line) { + if (line.hasOption("h")) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("Intialize Entities", options); + System.exit(0); + } + } + private static String getFileLocationFromCommandLine(CommandLine line) { + String query = line.getOptionValue("f"); + if (StringUtils.isEmpty(query)) { + System.out.println("No file location was entered"); + log.info("No file location was entered"); + System.exit(1); + } + return query; + } + + protected static Options createCommandLineOptions() { + Options options = new Options(); + options.addOption("f", "file", true, "the location for the file containing the xml data"); + + return options; + } + + private void run(String fileLocation) throws SQLException, AuthorizeException { + Context context = new Context(); + context.turnOffAuthorisationSystem(); + this.parseXMLToRelations(context, fileLocation); + context.complete(); + } + + private void parseXMLToRelations(Context context, String fileLocation) throws AuthorizeException { + try { + File fXmlFile = new File(fileLocation); + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = null; + dBuilder = dbFactory.newDocumentBuilder(); + Document doc = dBuilder.parse(fXmlFile); + + doc.getDocumentElement().normalize(); + + NodeList nList = doc.getElementsByTagName("type"); + List relationshipTypes = new LinkedList<>(); + for (int i = 0; i < nList.getLength(); i++) { + Node nNode = nList.item(i); + + if (nNode.getNodeType() == Node.ELEMENT_NODE) { + + Element eElement = (Element) nNode; + + String leftType = eElement.getElementsByTagName("leftType").item(0).getTextContent(); + String rightType = eElement.getElementsByTagName("rightType").item(0).getTextContent(); + String leftLabel = eElement.getElementsByTagName("leftLabel").item(0).getTextContent(); + String rightLabel = eElement.getElementsByTagName("rightLabel").item(0).getTextContent(); + + + NodeList leftCardinalityList = eElement.getElementsByTagName("leftCardinality"); + NodeList rightCardinalityList = eElement.getElementsByTagName("rightCardinality"); + + String leftCardinalityMin = ""; + String leftCardinalityMax = ""; + + String rightCardinalityMin = ""; + String rightCardinalityMax = ""; + + for (int j = 0; j < leftCardinalityList.getLength(); j++) { + Node node = leftCardinalityList.item(j); + leftCardinalityMin = getString(leftCardinalityMin,(Element) node, "min"); + leftCardinalityMax = getString(leftCardinalityMax,(Element) node, "max"); + + } + + for (int j = 0; j < rightCardinalityList.getLength(); j++) { + Node node = rightCardinalityList.item(j); + rightCardinalityMin = getString(rightCardinalityMin,(Element) node, "min"); + rightCardinalityMax = getString(rightCardinalityMax,(Element) node, "max"); + + } + populateRelationshipType(context, leftType, rightType, leftLabel, rightLabel, + leftCardinalityMin, leftCardinalityMax, + rightCardinalityMin, rightCardinalityMax); + + + } + } + } catch (ParserConfigurationException | SAXException | IOException | SQLException e) { + log.error("An error occurred while parsing the XML file to relations", e); + } + } + + private String getString(String leftCardinalityMin,Element node, String minOrMax) { + if (node.getElementsByTagName(minOrMax).getLength() > 0) { + leftCardinalityMin = node.getElementsByTagName(minOrMax).item(0).getTextContent(); + } + return leftCardinalityMin; + } + + private void populateRelationshipType(Context context, String leftType, String rightType, String leftLabel, + String rightLabel, String leftCardinalityMin, String leftCardinalityMax, + String rightCardinalityMin, String rightCardinalityMax) + throws SQLException, AuthorizeException { + + EntityType leftEntityType = entityTypeService.findByEntityType(context,leftType); + if (leftEntityType == null) { + leftEntityType = entityTypeService.create(context, leftType); + } + EntityType rightEntityType = entityTypeService.findByEntityType(context, rightType); + if (rightEntityType == null) { + rightEntityType = entityTypeService.create(context, rightType); + } + Integer leftCardinalityMinInteger; + Integer leftCardinalityMaxInteger; + Integer rightCardinalityMinInteger; + Integer rightCardinalityMaxInteger; + if (StringUtils.isNotBlank(leftCardinalityMin)) { + leftCardinalityMinInteger = Integer.parseInt(leftCardinalityMin); + } else { + leftCardinalityMinInteger = null; + } + if (StringUtils.isNotBlank(leftCardinalityMax)) { + leftCardinalityMaxInteger = Integer.parseInt(leftCardinalityMax); + } else { + leftCardinalityMaxInteger = null; + } + if (StringUtils.isNotBlank(rightCardinalityMin)) { + rightCardinalityMinInteger = Integer.parseInt(rightCardinalityMin); + } else { + rightCardinalityMinInteger = null; + } + if (StringUtils.isNotBlank(rightCardinalityMax)) { + rightCardinalityMaxInteger = Integer.parseInt(rightCardinalityMax); + } else { + rightCardinalityMaxInteger = null; + } + RelationshipType relationshipType = relationshipTypeService + .findbyTypesAndLabels(context, leftEntityType, rightEntityType, leftLabel, rightLabel); + if (relationshipType == null) { + relationshipTypeService.create(context, leftEntityType, rightEntityType, leftLabel, rightLabel, + leftCardinalityMinInteger, leftCardinalityMaxInteger, + rightCardinalityMinInteger, rightCardinalityMaxInteger); + } else { + relationshipType.setLeftMinCardinality(leftCardinalityMinInteger); + relationshipType.setLeftMaxCardinality(leftCardinalityMaxInteger); + relationshipType.setRightMinCardinality(rightCardinalityMinInteger); + relationshipType.setRightMaxCardinality(rightCardinalityMaxInteger); + relationshipTypeService.update(context, relationshipType); + } + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/Bitstream.java b/dspace-api/src/main/java/org/dspace/content/Bitstream.java index c18aa438aa..aa78c04996 100644 --- a/dspace-api/src/main/java/org/dspace/content/Bitstream.java +++ b/dspace-api/src/main/java/org/dspace/content/Bitstream.java @@ -130,7 +130,8 @@ public class Bitstream extends DSpaceObject implements DSpaceObjectLegacySupport */ @Override public String getName() { - return getBitstreamService().getMetadataFirstValue(this, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY); + return getBitstreamService().getMetadataFirstValue(this, MetadataSchemaEnum.DC.getName(), + "title", null, Item.ANY); } /** @@ -141,7 +142,8 @@ public class Bitstream extends DSpaceObject implements DSpaceObjectLegacySupport * @throws SQLException if database error */ public void setName(Context context, String n) throws SQLException { - getBitstreamService().setMetadataSingleValue(context, this, MetadataSchema.DC_SCHEMA, "title", null, null, n); + getBitstreamService().setMetadataSingleValue(context, this, MetadataSchemaEnum.DC.getName(), + "title", null, null, n); } /** @@ -152,7 +154,8 @@ public class Bitstream extends DSpaceObject implements DSpaceObjectLegacySupport * @return the source of the bitstream */ public String getSource() { - return getBitstreamService().getMetadataFirstValue(this, MetadataSchema.DC_SCHEMA, "source", null, Item.ANY); + return getBitstreamService().getMetadataFirstValue(this, MetadataSchemaEnum.DC.getName(), + "source", null, Item.ANY); } /** @@ -163,7 +166,8 @@ public class Bitstream extends DSpaceObject implements DSpaceObjectLegacySupport * @throws SQLException if database error */ public void setSource(Context context, String n) throws SQLException { - getBitstreamService().setMetadataSingleValue(context, this, MetadataSchema.DC_SCHEMA, "source", null, null, n); + getBitstreamService().setMetadataSingleValue(context, this, MetadataSchemaEnum.DC.getName(), + "source", null, null, n); } /** @@ -174,7 +178,8 @@ public class Bitstream extends DSpaceObject implements DSpaceObjectLegacySupport */ public String getDescription() { return getBitstreamService() - .getMetadataFirstValue(this, MetadataSchema.DC_SCHEMA, "description", null, Item.ANY); + .getMetadataFirstValue(this, MetadataSchemaEnum.DC.getName(), "description", + null, Item.ANY); } /** @@ -186,7 +191,7 @@ public class Bitstream extends DSpaceObject implements DSpaceObjectLegacySupport */ public void setDescription(Context context, String n) throws SQLException { getBitstreamService() - .setMetadataSingleValue(context, this, MetadataSchema.DC_SCHEMA, "description", null, null, n); + .setMetadataSingleValue(context, this, MetadataSchemaEnum.DC.getName(), "description", null, null, n); } /** @@ -235,7 +240,8 @@ public class Bitstream extends DSpaceObject implements DSpaceObjectLegacySupport * @return the user's format description. */ public String getUserFormatDescription() { - return getBitstreamService().getMetadataFirstValue(this, MetadataSchema.DC_SCHEMA, "format", null, Item.ANY); + return getBitstreamService().getMetadataFirstValue(this, MetadataSchemaEnum.DC.getName(), + "format", null, Item.ANY); } protected BitstreamFormat getBitstreamFormat() { diff --git a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java index bc076443ed..78d65800b1 100644 --- a/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BitstreamServiceImpl.java @@ -205,7 +205,7 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp @Override public void setUserFormatDescription(Context context, Bitstream bitstream, String desc) throws SQLException { setFormat(context, bitstream, null); - setMetadataSingleValue(context, bitstream, MetadataSchema.DC_SCHEMA, "format", null, null, desc); + setMetadataSingleValue(context, bitstream, MetadataSchemaEnum.DC.getName(), "format", null, null, desc); } @Override @@ -235,7 +235,7 @@ public class BitstreamServiceImpl extends DSpaceObjectServiceImpl imp } // Remove user type description - clearMetadata(context, bitstream, MetadataSchema.DC_SCHEMA, "format", null, Item.ANY); + clearMetadata(context, bitstream, MetadataSchemaEnum.DC.getName(), "format", null, Item.ANY); // Update the ID in the table row bitstream.setFormat(bitstreamFormat); diff --git a/dspace-api/src/main/java/org/dspace/content/Bundle.java b/dspace-api/src/main/java/org/dspace/content/Bundle.java index 5fe7100d4f..88f21c2c2f 100644 --- a/dspace-api/src/main/java/org/dspace/content/Bundle.java +++ b/dspace-api/src/main/java/org/dspace/content/Bundle.java @@ -88,7 +88,7 @@ public class Bundle extends DSpaceObject implements DSpaceObjectLegacySupport { */ @Override public String getName() { - return getBundleService().getMetadataFirstValue(this, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY); + return getBundleService().getMetadataFirstValue(this, MetadataSchemaEnum.DC.getName(), "title", null, Item.ANY); } /** @@ -100,7 +100,8 @@ public class Bundle extends DSpaceObject implements DSpaceObjectLegacySupport { * @throws SQLException if database error */ public void setName(Context context, String name) throws SQLException { - getBundleService().setMetadataSingleValue(context, this, MetadataSchema.DC_SCHEMA, "title", null, null, name); + getBundleService().setMetadataSingleValue(context, this, MetadataSchemaEnum.DC.getName(), + "title", null, null, name); } /** diff --git a/dspace-api/src/main/java/org/dspace/content/Collection.java b/dspace-api/src/main/java/org/dspace/content/Collection.java index e7c0b17d4c..0d6a662b29 100644 --- a/dspace-api/src/main/java/org/dspace/content/Collection.java +++ b/dspace-api/src/main/java/org/dspace/content/Collection.java @@ -120,7 +120,7 @@ public class Collection extends DSpaceObject implements DSpaceObjectLegacySuppor @Override public String getName() { String value = getCollectionService() - .getMetadataFirstValue(this, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY); + .getMetadataFirstValue(this, MetadataSchemaEnum.DC.getName(), "title", null, Item.ANY); return value == null ? "" : value; } diff --git a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java index f52ee6ec3f..acb44a31a5 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -153,10 +153,11 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i @Override public List findAll(Context context) throws SQLException { - MetadataField nameField = metadataFieldService.findByElement(context, MetadataSchema.DC_SCHEMA, "title", null); + MetadataField nameField = metadataFieldService.findByElement(context, MetadataSchemaEnum.DC.getName(), + "title", null); if (nameField == null) { throw new IllegalArgumentException( - "Required metadata field '" + MetadataSchema.DC_SCHEMA + ".title' doesn't exist!"); + "Required metadata field '" + MetadataSchemaEnum.DC.getName() + ".title' doesn't exist!"); } return collectionDAO.findAll(context, nameField); @@ -164,10 +165,11 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i @Override public List findAll(Context context, Integer limit, Integer offset) throws SQLException { - MetadataField nameField = metadataFieldService.findByElement(context, MetadataSchema.DC_SCHEMA, "title", null); + MetadataField nameField = metadataFieldService.findByElement(context, MetadataSchemaEnum.DC.getName(), + "title", null); if (nameField == null) { throw new IllegalArgumentException( - "Required metadata field '" + MetadataSchema.DC_SCHEMA + ".title' doesn't exist!"); + "Required metadata field '" + MetadataSchemaEnum.DC.getName() + ".title' doesn't exist!"); } return collectionDAO.findAll(context, nameField, limit, offset); diff --git a/dspace-api/src/main/java/org/dspace/content/Community.java b/dspace-api/src/main/java/org/dspace/content/Community.java index b4944297bf..aecd03fcd6 100644 --- a/dspace-api/src/main/java/org/dspace/content/Community.java +++ b/dspace-api/src/main/java/org/dspace/content/Community.java @@ -262,7 +262,7 @@ public class Community extends DSpaceObject implements DSpaceObjectLegacySupport @Override public String getName() { String value = getCommunityService() - .getMetadataFirstValue(this, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY); + .getMetadataFirstValue(this, MetadataSchemaEnum.DC.getName(), "title", null, Item.ANY); return value == null ? "" : value; } diff --git a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java index 26c663c982..242a5ff389 100644 --- a/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CommunityServiceImpl.java @@ -148,10 +148,11 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp @Override public List findAll(Context context) throws SQLException { - MetadataField sortField = metadataFieldService.findByElement(context, MetadataSchema.DC_SCHEMA, "title", null); + MetadataField sortField = metadataFieldService.findByElement(context, MetadataSchemaEnum.DC.getName(), + "title", null); if (sortField == null) { throw new IllegalArgumentException( - "Required metadata field '" + MetadataSchema.DC_SCHEMA + ".title' doesn't exist!"); + "Required metadata field '" + MetadataSchemaEnum.DC.getName() + ".title' doesn't exist!"); } return communityDAO.findAll(context, sortField); @@ -159,10 +160,11 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp @Override public List findAll(Context context, Integer limit, Integer offset) throws SQLException { - MetadataField nameField = metadataFieldService.findByElement(context, MetadataSchema.DC_SCHEMA, "title", null); + MetadataField nameField = metadataFieldService.findByElement(context, MetadataSchemaEnum.DC.getName(), + "title", null); if (nameField == null) { throw new IllegalArgumentException( - "Required metadata field '" + MetadataSchema.DC_SCHEMA + ".title' doesn't exist!"); + "Required metadata field '" + MetadataSchemaEnum.DC.getName() + ".title' doesn't exist!"); } return communityDAO.findAll(context, nameField, limit, offset); @@ -171,10 +173,11 @@ public class CommunityServiceImpl extends DSpaceObjectServiceImpl imp @Override public List findAllTop(Context context) throws SQLException { // get all communities that are not children - MetadataField sortField = metadataFieldService.findByElement(context, MetadataSchema.DC_SCHEMA, "title", null); + MetadataField sortField = metadataFieldService.findByElement(context, MetadataSchemaEnum.DC.getName(), + "title", null); if (sortField == null) { throw new IllegalArgumentException( - "Required metadata field '" + MetadataSchema.DC_SCHEMA + ".title' doesn't exist!"); + "Required metadata field '" + MetadataSchemaEnum.DC.getName() + ".title' doesn't exist!"); } return communityDAO.findAllNoParent(context, sortField); diff --git a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java index b2dbb97fec..7f3f97822a 100644 --- a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java @@ -10,8 +10,10 @@ package org.dspace.content; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.StringTokenizer; @@ -27,6 +29,7 @@ import org.dspace.content.authority.service.MetadataAuthorityService; import org.dspace.content.service.DSpaceObjectService; import org.dspace.content.service.MetadataFieldService; import org.dspace.content.service.MetadataValueService; +import org.dspace.content.service.RelationshipService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.handle.service.HandleService; @@ -60,6 +63,8 @@ public abstract class DSpaceObjectServiceImpl implements protected MetadataFieldService metadataFieldService; @Autowired(required = true) protected MetadataAuthorityService metadataAuthorityService; + @Autowired(required = true) + protected RelationshipService relationshipService; public DSpaceObjectServiceImpl() { @@ -67,7 +72,7 @@ public abstract class DSpaceObjectServiceImpl implements @Override public String getName(T dso) { - String value = getMetadataFirstValue(dso, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY); + String value = getMetadataFirstValue(dso, MetadataSchemaEnum.DC.getName(), "title", null, Item.ANY); return value == null ? "" : value; } @@ -235,12 +240,19 @@ public abstract class DSpaceObjectServiceImpl implements throws SQLException { boolean authorityControlled = metadataAuthorityService.isAuthorityControlled(metadataField); boolean authorityRequired = metadataAuthorityService.isAuthorityRequired(metadataField); - // We will not verify that they are valid entries in the registry // until update() is called. for (int i = 0; i < values.size(); i++) { + if (authorities != null && authorities.size() >= i) { + if (StringUtils.startsWith(authorities.get(i), Constants.VIRTUAL_AUTHORITY_PREFIX)) { + continue; + } + } MetadataValue metadataValue = metadataValueService.create(context, dso, metadataField); + //Set place to list length + metadataValue.setPlace(this.getMetadata(dso, Item.ANY, Item.ANY, Item.ANY, Item.ANY).size()); + metadataValue.setLanguage(lang == null ? null : lang.trim()); // Logic to set Authority and Confidence: @@ -543,11 +555,50 @@ public abstract class DSpaceObjectServiceImpl implements */ // A map created to store the latest place for each metadata field Map fieldToLastPlace = new HashMap<>(); - List metadataValues = dso.getMetadata(); + List metadataValues = new LinkedList<>(); + if (dso.getType() == Constants.ITEM) { + metadataValues = getMetadata(dso, Item.ANY, Item.ANY, Item.ANY, Item.ANY); + } else { + metadataValues = dso.getMetadata(); + } + //This inline sort function will sort the MetadataValues based on their place in ascending order + //If two places are the same then the MetadataValue instance will be placed before the + //RelationshipMetadataValue instance. + //This is done to ensure that the order is correct. + metadataValues.sort(new Comparator() { + public int compare(MetadataValue o1, MetadataValue o2) { + int compare = o1.getPlace() - o2.getPlace(); + if (compare == 0) { + if (o1 instanceof RelationshipMetadataValue) { + return 1; + } else if (o2 instanceof RelationshipMetadataValue) { + return -1; + } + } + return compare; + } + }); for (MetadataValue metadataValue : metadataValues) { //Retrieve & store the place for each metadata value - int mvPlace = getMetadataValuePlace(fieldToLastPlace, metadataValue); - metadataValue.setPlace(mvPlace); + if (StringUtils.startsWith(metadataValue.getAuthority(), Constants.VIRTUAL_AUTHORITY_PREFIX) && + ((RelationshipMetadataValue) metadataValue).isUseForPlace()) { + int mvPlace = getMetadataValuePlace(fieldToLastPlace, metadataValue); + metadataValue.setPlace(mvPlace); + String authority = metadataValue.getAuthority(); + String relationshipId = StringUtils.split(authority, "::")[1]; + Relationship relationship = relationshipService.find(context, Integer.parseInt(relationshipId)); + if (relationship.getLeftItem() == (Item) dso) { + relationship.setLeftPlace(mvPlace); + } else { + relationship.setRightPlace(mvPlace); + } + relationshipService.update(context, relationship); + + } else if (!StringUtils.startsWith(metadataValue.getAuthority(), + Constants.VIRTUAL_AUTHORITY_PREFIX)) { + int mvPlace = getMetadataValuePlace(fieldToLastPlace, metadataValue); + metadataValue.setPlace(mvPlace); + } } } } @@ -557,7 +608,7 @@ public abstract class DSpaceObjectServiceImpl implements * * @param fieldToLastPlace the map containing the latest place of each metadata field * @param metadataValue the metadata value that needs to get a place - * @return The new place for the metadata valu + * @return The new place for the metadata value */ protected int getMetadataValuePlace(Map fieldToLastPlace, MetadataValue metadataValue) { MetadataField metadataField = metadataValue.getMetadataField(); @@ -573,23 +624,23 @@ public abstract class DSpaceObjectServiceImpl implements protected String[] getMDValueByLegacyField(String field) { switch (field) { case "introductory_text": - return new String[] {MetadataSchema.DC_SCHEMA, "description", null}; + return new String[] {MetadataSchemaEnum.DC.getName(), "description", null}; case "short_description": - return new String[] {MetadataSchema.DC_SCHEMA, "description", "abstract"}; + return new String[] {MetadataSchemaEnum.DC.getName(), "description", "abstract"}; case "side_bar_text": - return new String[] {MetadataSchema.DC_SCHEMA, "description", "tableofcontents"}; + return new String[] {MetadataSchemaEnum.DC.getName(), "description", "tableofcontents"}; case "copyright_text": - return new String[] {MetadataSchema.DC_SCHEMA, "rights", null}; + return new String[] {MetadataSchemaEnum.DC.getName(), "rights", null}; case "name": - return new String[] {MetadataSchema.DC_SCHEMA, "title", null}; + return new String[] {MetadataSchemaEnum.DC.getName(), "title", null}; case "provenance_description": - return new String[] {MetadataSchema.DC_SCHEMA, "provenance", null}; + return new String[] {MetadataSchemaEnum.DC.getName(), "provenance", null}; case "license": - return new String[] {MetadataSchema.DC_SCHEMA, "rights", "license"}; + return new String[] {MetadataSchemaEnum.DC.getName(), "rights", "license"}; case "user_format_description": - return new String[] {MetadataSchema.DC_SCHEMA, "format", null}; + return new String[] {MetadataSchemaEnum.DC.getName(), "format", null}; case "source": - return new String[] {MetadataSchema.DC_SCHEMA, "source", null}; + return new String[] {MetadataSchemaEnum.DC.getName(), "source", null}; case "firstname": return new String[] {"eperson", "firstname", null}; case "lastname": diff --git a/dspace-api/src/main/java/org/dspace/content/Entity.java b/dspace-api/src/main/java/org/dspace/content/Entity.java new file mode 100644 index 0000000000..108939e94e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/Entity.java @@ -0,0 +1,68 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content; + +import java.util.List; + +/** + * This class represents an Entity object. An Entity object has an Item that it describes with a list + * of relationships that it includes as well. + */ +public class Entity { + + /** + * The Item that is being described by this Entity + */ + private Item item; + /** + * The relationships for the Item that is included in this Entity + */ + private List relationships; + + /** + * constructor for the Entity object + * @param item The Item to be included in this Entity object as a property + * @param relationshipList The list of relationships + */ + public Entity(Item item,List relationshipList) { + setItem(item); + setRelationships(relationshipList); + } + + /** + * Standard getter for the Item for this Entity object + * @return The Item that is described in this Entity object + */ + public Item getItem() { + return item; + } + + /** + * Standard setter for the Item for this Entity object + * @param item The Item to be set + */ + public void setItem(Item item) { + this.item = item; + } + + /** + * Standard getter for the list of relationships for the Item in this Entity object + * @return the list of relationships + */ + public List getRelationships() { + return relationships; + } + + /** + * Standard setter for the list of relationships for the Item in this Entity object + * @param relationships The list of relationships to be set + */ + public void setRelationships(List relationships) { + this.relationships = relationships; + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/EntityServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/EntityServiceImpl.java new file mode 100644 index 0000000000..07b2b3062d --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/EntityServiceImpl.java @@ -0,0 +1,142 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content; + +import java.sql.SQLException; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.content.service.EntityService; +import org.dspace.content.service.EntityTypeService; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.RelationshipService; +import org.dspace.content.service.RelationshipTypeService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +public class EntityServiceImpl implements EntityService { + + @Autowired(required = true) + protected EntityTypeService entityTypeService; + + @Autowired(required = true) + protected RelationshipService relationshipService; + + @Autowired(required = true) + protected RelationshipTypeService relationshipTypeService; + + @Autowired(required = true) + protected ItemService itemService; + + @Override + public Entity findByItemId(Context context, UUID itemId) throws SQLException { + Item item = itemService.find(context, itemId); + List relationshipList = relationshipService.findByItem(context, item); + return new Entity(item, relationshipList); + } + + @Override + public EntityType getType(Context context, Entity entity) throws SQLException { + Item item = entity.getItem(); + List list = itemService.getMetadata(item, "relationship", "type", null, Item.ANY); + if (!list.isEmpty()) { + return entityTypeService.findByEntityType(context, list.get(0).getValue()); + } else { + return null; + } + } + + @Override + public List getLeftRelations(Context context, Entity entity) { + List fullList = entity.getRelationships(); + List listToReturn = new LinkedList<>(); + for (Relationship relationship : fullList) { + if (relationship.getLeftItem().getID() == entity.getItem().getID()) { + listToReturn.add(relationship); + } + } + return listToReturn; + } + + @Override + public List getRightRelations(Context context, Entity entity) { + List fullList = entity.getRelationships(); + List listToReturn = new LinkedList<>(); + for (Relationship relationship : fullList) { + if (relationship.getRightItem().getID() == entity.getItem().getID()) { + listToReturn.add(relationship); + } + } + return listToReturn; + } + + @Override + public List getRelationsByLabel(Context context, String label) throws SQLException { + List listToReturn = new LinkedList<>(); + List relationshipList = relationshipService.findAll(context); + for (Relationship relationship : relationshipList) { + RelationshipType relationshipType = relationship.getRelationshipType(); + if (StringUtils.equals(relationshipType.getLeftLabel(),label) || + StringUtils.equals(relationshipType.getRightLabel(),label)) { + listToReturn.add(relationship); + } + } + return listToReturn; + } + + @Override + public List getAllRelationshipTypes(Context context, Entity entity) throws SQLException { + EntityType entityType = this.getType(context, entity); + List listToReturn = new LinkedList<>(); + for (RelationshipType relationshipType : relationshipTypeService.findAll(context)) { + if (relationshipType.getLeftType().getID() == entityType.getID() || + relationshipType.getRightType().getID() == entityType.getID()) { + listToReturn.add(relationshipType); + } + } + return listToReturn; + } + + @Override + public List getLeftRelationshipTypes(Context context, Entity entity) throws SQLException { + EntityType entityType = this.getType(context, entity); + List listToReturn = new LinkedList<>(); + for (RelationshipType relationshipType : relationshipTypeService.findAll(context)) { + if (relationshipType.getLeftType().getID() == entityType.getID()) { + listToReturn.add(relationshipType); + } + } + return listToReturn; + } + + @Override + public List getRightRelationshipTypes(Context context, Entity entity) throws SQLException { + EntityType entityType = this.getType(context, entity); + List listToReturn = new LinkedList<>(); + for (RelationshipType relationshipType : relationshipTypeService.findAll(context)) { + if (relationshipType.getRightType().getID() == entityType.getID()) { + listToReturn.add(relationshipType); + } + } + return listToReturn; + } + + @Override + public List getRelationshipTypesByLabel(Context context, String label) throws SQLException { + List listToReturn = new LinkedList<>(); + for (RelationshipType relationshipType : relationshipTypeService.findAll(context)) { + if (StringUtils.equals(relationshipType.getLeftLabel(),label) || + StringUtils.equals(relationshipType.getRightLabel(),label)) { + listToReturn.add(relationshipType); + } + } + return listToReturn; + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/EntityType.java b/dspace-api/src/main/java/org/dspace/content/EntityType.java new file mode 100644 index 0000000000..15fe1739e5 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/EntityType.java @@ -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.content; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; + +import org.dspace.core.ReloadableEntity; + +/** + * Class representing an EntityType + * This class contains an Integer ID that will be the unique value for this class and also the primary key + * This also has a label that will be used to identify what kind of EntityType this object is + */ +@Entity +@Table(name = "entity_type") +public class EntityType implements ReloadableEntity { + + /** + * The Integer ID used as a primary key for this database object. + * This is generated by a sequence + */ + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "entity_type_id_seq") + @SequenceGenerator(name = "entity_type_id_seq", sequenceName = "entity_type_id_seq", allocationSize = 1) + @Column(name = "id", unique = true, nullable = false, insertable = true, updatable = false) + protected Integer id; + + /** + * The String label field for the entity type + * This cannot be null + */ + @Column(name = "label", nullable = false) + private String label; + + /** + * The standard setter for the ID of this EntityType + * @param id The ID that this EntityType's ID will be set to + */ + public void setId(Integer id) { + this.id = id; + } + + /** + * The standard getter for the label of this EntityType + * @return The label for this EntityType + */ + public String getLabel() { + return label; + } + + /** + * The standard setter for the label of this EntityType + * @param label The label that this EntityType's label will be set to + */ + public void setLabel(String label) { + this.label = label; + } + + /** + * The standard getter for the ID of this EntityType + * @return The ID for this EntityType + */ + public Integer getID() { + return id; + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java new file mode 100644 index 0000000000..e35a8a20e4 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/EntityTypeServiceImpl.java @@ -0,0 +1,94 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content; + +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.collections.CollectionUtils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.dao.EntityTypeDAO; +import org.dspace.content.service.EntityTypeService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +public class EntityTypeServiceImpl implements EntityTypeService { + + @Autowired(required = true) + protected EntityTypeDAO entityTypeDAO; + + @Autowired(required = true) + protected AuthorizeService authorizeService; + + @Override + public EntityType findByEntityType(Context context,String entityType) throws SQLException { + return entityTypeDAO.findByEntityType(context, entityType); + } + + @Override + public List findAll(Context context) throws SQLException { + return entityTypeDAO.findAll(context, EntityType.class); + } + + @Override + public EntityType create(Context context) throws SQLException, AuthorizeException { + if (!authorizeService.isAdmin(context)) { + throw new AuthorizeException( + "Only administrators can modify entityType"); + } + return entityTypeDAO.create(context, new EntityType()); + } + + @Override + public EntityType create(Context context, String entityTypeString) throws SQLException, AuthorizeException { + if (!authorizeService.isAdmin(context)) { + throw new AuthorizeException( + "Only administrators can modify entityType"); + } + EntityType entityType = new EntityType(); + entityType.setLabel(entityTypeString); + return entityTypeDAO.create(context, entityType); + } + + @Override + public EntityType find(Context context,int id) throws SQLException { + EntityType entityType = entityTypeDAO.findByID(context, EntityType.class, id); + return entityType; + } + + @Override + public void update(Context context,EntityType entityType) throws SQLException, AuthorizeException { + update(context,Collections.singletonList(entityType)); + } + + @Override + public void update(Context context,List entityTypes) throws SQLException, AuthorizeException { + if (CollectionUtils.isNotEmpty(entityTypes)) { + // Check authorisation - only administrators can change formats + if (!authorizeService.isAdmin(context)) { + throw new AuthorizeException( + "Only administrators can modify entityType"); + } + + for (EntityType entityType : entityTypes) { + entityTypeDAO.save(context, entityType); + } + } + } + + @Override + public void delete(Context context,EntityType entityType) throws SQLException, AuthorizeException { + if (!authorizeService.isAdmin(context)) { + throw new AuthorizeException( + "Only administrators can delete entityType"); + } + entityTypeDAO.delete(context, entityType); + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java index 59298e4c31..11cd4c107c 100644 --- a/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/InstallItemServiceImpl.java @@ -104,9 +104,10 @@ public class InstallItemServiceImpl implements InstallItemService { // If the item doesn't have a date.accessioned, set it to today List dateAccessioned = itemService - .getMetadata(item, MetadataSchema.DC_SCHEMA, "date", "accessioned", Item.ANY); + .getMetadata(item, MetadataSchemaEnum.DC.getName(), "date", "accessioned", Item.ANY); if (dateAccessioned.isEmpty()) { - itemService.addMetadata(c, item, MetadataSchema.DC_SCHEMA, "date", "accessioned", null, now.toString()); + itemService.addMetadata(c, item, MetadataSchemaEnum.DC.getName(), + "date", "accessioned", null, now.toString()); } // If issue date is set as "today" (literal string), then set it to current date @@ -114,8 +115,8 @@ public class InstallItemServiceImpl implements InstallItemService { // replacing "today" with today's date. // NOTE: As of DSpace 4.0, DSpace no longer sets an issue date by default List currentDateIssued = itemService - .getMetadata(item, MetadataSchema.DC_SCHEMA, "date", "issued", Item.ANY); - itemService.clearMetadata(c, item, MetadataSchema.DC_SCHEMA, "date", "issued", Item.ANY); + .getMetadata(item, MetadataSchemaEnum.DC.getName(), "date", "issued", Item.ANY); + itemService.clearMetadata(c, item, MetadataSchemaEnum.DC.getName(), "date", "issued", Item.ANY); for (MetadataValue dcv : currentDateIssued) { if (dcv.getValue() != null && dcv.getValue().equalsIgnoreCase("today")) { DCDate issued = new DCDate(now.getYear(), now.getMonth(), now.getDay(), -1, -1, -1); @@ -127,7 +128,8 @@ public class InstallItemServiceImpl implements InstallItemService { // Record that the item was restored String provDescription = "Restored into DSpace on " + now + " (GMT)."; - itemService.addMetadata(c, item, MetadataSchema.DC_SCHEMA, "description", "provenance", "en", provDescription); + itemService.addMetadata(c, item, MetadataSchemaEnum.DC.getName(), + "description", "provenance", "en", provDescription); return finishItem(c, item, is); } @@ -137,14 +139,16 @@ public class InstallItemServiceImpl implements InstallItemService { throws SQLException, AuthorizeException { // create accession date DCDate now = DCDate.getCurrent(); - itemService.addMetadata(c, item, MetadataSchema.DC_SCHEMA, "date", "accessioned", null, now.toString()); + itemService.addMetadata(c, item, MetadataSchemaEnum.DC.getName(), + "date", "accessioned", null, now.toString()); // add date available if not under embargo, otherwise it will // be set when the embargo is lifted. // this will flush out fatal embargo metadata // problems before we set inArchive. if (embargoService.getEmbargoTermsAsDate(c, item) == null) { - itemService.addMetadata(c, item, MetadataSchema.DC_SCHEMA, "date", "available", null, now.toString()); + itemService.addMetadata(c, item, MetadataSchemaEnum.DC.getName(), + "date", "available", null, now.toString()); } // If issue date is set as "today" (literal string), then set it to current date @@ -152,8 +156,8 @@ public class InstallItemServiceImpl implements InstallItemService { // replacing "today" with today's date. // NOTE: As of DSpace 4.0, DSpace no longer sets an issue date by default List currentDateIssued = itemService - .getMetadata(item, MetadataSchema.DC_SCHEMA, "date", "issued", Item.ANY); - itemService.clearMetadata(c, item, MetadataSchema.DC_SCHEMA, "date", "issued", Item.ANY); + .getMetadata(item, MetadataSchemaEnum.DC.getName(), "date", "issued", Item.ANY); + itemService.clearMetadata(c, item, MetadataSchemaEnum.DC.getName(), "date", "issued", Item.ANY); for (MetadataValue dcv : currentDateIssued) { if (dcv.getValue() != null && dcv.getValue().equalsIgnoreCase("today")) { DCDate issued = new DCDate(now.getYear(), now.getMonth(), now.getDay(), -1, -1, -1); @@ -178,7 +182,8 @@ public class InstallItemServiceImpl implements InstallItemService { } // Add provenance description - itemService.addMetadata(c, item, MetadataSchema.DC_SCHEMA, "description", "provenance", "en", provDescription); + itemService.addMetadata(c, item, MetadataSchemaEnum.DC.getName(), + "description", "provenance", "en", provDescription); } /** diff --git a/dspace-api/src/main/java/org/dspace/content/Item.java b/dspace-api/src/main/java/org/dspace/content/Item.java index 8c32eaf553..adb2eaaacd 100644 --- a/dspace-api/src/main/java/org/dspace/content/Item.java +++ b/dspace-api/src/main/java/org/dspace/content/Item.java @@ -362,7 +362,7 @@ public class Item extends DSpaceObject implements DSpaceObjectLegacySupport, Ind @Override public String getName() { - return getItemService().getMetadataFirstValue(this, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY); + return getItemService().getMetadataFirstValue(this, MetadataSchemaEnum.DC.getName(), "title", null, Item.ANY); } @Override diff --git a/dspace-api/src/main/java/org/dspace/content/ItemComparator.java b/dspace-api/src/main/java/org/dspace/content/ItemComparator.java index 2daa61cf57..63182e4aae 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemComparator.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemComparator.java @@ -174,7 +174,7 @@ public class ItemComparator implements Comparator, Serializable { protected String getValue(Item item) { // The overall array and each element are guaranteed non-null List dcvalues = itemService - .getMetadata(item, MetadataSchema.DC_SCHEMA, element, qualifier, language); + .getMetadata(item, MetadataSchemaEnum.DC.getName(), element, qualifier, language); if (dcvalues.isEmpty()) { return null; diff --git a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java index 27be2bd298..8345f6ba82 100644 --- a/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/ItemServiceImpl.java @@ -11,10 +11,16 @@ import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Comparator; import java.util.Date; +import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -36,7 +42,10 @@ import org.dspace.content.service.CommunityService; import org.dspace.content.service.InstallItemService; import org.dspace.content.service.ItemService; import org.dspace.content.service.MetadataSchemaService; +import org.dspace.content.service.RelationshipService; import org.dspace.content.service.WorkspaceItemService; +import org.dspace.content.virtual.VirtualMetadataConfiguration; +import org.dspace.content.virtual.VirtualMetadataPopulator; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.LogManager; @@ -102,6 +111,11 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It @Autowired(required = true) protected WorkflowItemService workflowItemService; + @Autowired(required = true) + protected RelationshipService relationshipService; + + @Autowired(required = true) + protected VirtualMetadataPopulator virtualMetadataPopulator; protected ItemServiceImpl() { super(); @@ -230,10 +244,10 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It throws SQLException { MetadataField metadataField = metadataFieldService - .findByElement(context, MetadataSchema.DC_SCHEMA, "date", "accessioned"); + .findByElement(context, MetadataSchemaEnum.DC.getName(), "date", "accessioned"); if (metadataField == null) { throw new IllegalArgumentException( - "Required metadata field '" + MetadataSchema.DC_SCHEMA + ".date.accessioned' doesn't exist!"); + "Required metadata field '" + MetadataSchemaEnum.DC.getName() + ".date.accessioned' doesn't exist!"); } return itemDAO.findBySubmitter(context, eperson, metadataField, limit); @@ -554,7 +568,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It prov.append(installItemService.getBitstreamProvenanceMessage(context, item)); - addMetadata(context, item, MetadataSchema.DC_SCHEMA, "description", "provenance", "en", prov.toString()); + addMetadata(context, item, MetadataSchemaEnum.DC.getName(), "description", "provenance", "en", prov.toString()); // Update item in DB update(context, item); @@ -609,7 +623,7 @@ public class ItemServiceImpl extends DSpaceObjectServiceImpl implements It // bitstream checksums prov.append(installItemService.getBitstreamProvenanceMessage(context, item)); - addMetadata(context, item, MetadataSchema.DC_SCHEMA, "description", "provenance", "en", prov.toString()); + addMetadata(context, item, MetadataSchemaEnum.DC.getName(), "description", "provenance", "en", prov.toString()); // Update item in DB update(context, item); @@ -1206,7 +1220,7 @@ prevent the generation of resource policy entry values with null dspace_object a @Override public int countAllItems(Context context, Collection collection) throws SQLException { return itemDAO.countItems(context, collection, true, false) + itemDAO.countItems(context, collection, - false, true); + false, true); } @Override @@ -1225,7 +1239,7 @@ prevent the generation of resource policy entry values with null dspace_object a // Now, lets count unique items across that list of collections return itemDAO.countItems(context, collections, true, false) + itemDAO.countItems(context, collections, - false, true); + false, true); } @Override @@ -1294,4 +1308,246 @@ prevent the generation of resource policy entry values with null dspace_object a return false; } -} + /** + * This method will return a list of MetadataValue objects that contains all the regular + * metadata of the item passed along in the parameters as well as all the virtual metadata + * which will be generated and processed together with the {@link VirtualMetadataPopulator} + * by processing the item's relationships + * @param item the Item to be processed + * @param schema the schema for the metadata field. Must match + * the name of an existing metadata schema. + * @param element the element name. DSpaceObject.ANY matches any + * element. null doesn't really make sense as all + * metadata must have an element. + * @param qualifier the qualifier. null means unqualified, and + * DSpaceObject.ANY means any qualifier (including + * unqualified.) + * @param lang the ISO639 language code, optionally followed by an underscore + * and the ISO3166 country code. null means only + * values with no language are returned, and + * DSpaceObject.ANY means values with any country code or + * no country code are returned. + * @return + */ + @Override + public List getMetadata(Item item, String schema, String element, String qualifier, String lang) { + return this.getMetadata(item, schema, element, qualifier, lang, true); + } + + @Override + public List getRelationshipMetadata(Item item, boolean enableVirtualMetadata) { + Context context = new Context(); + List fullMetadataValueList = new LinkedList<>(); + try { + List list = item.getMetadata(); + String entityType = getEntityTypeStringFromMetadata(list); + if (StringUtils.isNotBlank(entityType)) { + List relationships = relationshipService.findByItem(context, item); + for (Relationship relationship : relationships) { + fullMetadataValueList + .addAll(handleItemRelationship(context, item, entityType, relationship, enableVirtualMetadata)); + } + + } + } catch (SQLException e) { + log.error("Lookup for Relationships for item with uuid: " + item.getID() + " caused DSpace to crash", e); + } + return fullMetadataValueList; + } + + @Override + public List getMetadata(Item item, String schema, String element, String qualifier, String lang, + boolean enableVirtualMetadata) { + //Fields of the relation schema are virtual metadata + //except for relation.type which is the type of item in the model + if (StringUtils.equals(schema, MetadataSchemaEnum.RELATION.getName()) && !StringUtils.equals(element, "type")) { + + List relationMetadata = getRelationshipMetadata(item, false); + List listToReturn = new LinkedList<>(); + for (MetadataValue metadataValue : relationMetadata) { + if (StringUtils.equals(metadataValue.getMetadataField().getElement(), element)) { + listToReturn.add(metadataValue); + } + } + listToReturn = sortMetadataValueList(listToReturn); + + return listToReturn; + + } else { + List dbMetadataValues = super.getMetadata(item, schema, element, qualifier, lang); + + List fullMetadataValueList = new LinkedList<>(); + if (enableVirtualMetadata) { + fullMetadataValueList.addAll(getRelationshipMetadata(item, true)); + + } + fullMetadataValueList.addAll(dbMetadataValues); + + List finalList = new LinkedList<>(); + for (MetadataValue metadataValue : fullMetadataValueList) { + if (match(schema, element, qualifier, lang, metadataValue)) { + finalList.add(metadataValue); + } + } + finalList = sortMetadataValueList(finalList); + return finalList; + } + + } + + /** + * This method will sort the List of MetadataValue objects based on the MetadataSchema, MetadataField Element, + * MetadataField Qualifier and MetadataField Place in that order. + * @param listToReturn The list to be sorted + * @return The list sorted on those criteria + */ + private List sortMetadataValueList(List listToReturn) { + Comparator comparator = Comparator.comparing( + metadataValue -> metadataValue.getMetadataField().getMetadataSchema().getName(), + Comparator.nullsFirst(Comparator.naturalOrder())); + comparator = comparator.thenComparing(metadataValue -> metadataValue.getMetadataField().getElement(), + Comparator.nullsFirst(Comparator.naturalOrder())); + comparator = comparator.thenComparing(metadataValue -> metadataValue.getMetadataField().getQualifier(), + Comparator.nullsFirst(Comparator.naturalOrder())); + comparator = comparator.thenComparing(metadataValue -> metadataValue.getPlace(), + Comparator.nullsFirst(Comparator.naturalOrder())); + + Stream metadataValueStream = listToReturn.stream().sorted(comparator); + listToReturn = metadataValueStream.collect(Collectors.toList()); + return listToReturn; + } + + //This method processes the Relationship of an Item and will return a list of RelationshipMetadataValue objects + //that are generated for this specfic relationship for the item through the config in VirtualMetadataPopulator + private List handleItemRelationship(Context context, Item item, String entityType, + Relationship relationship, + boolean enableVirtualMetadata) + throws SQLException { + List resultingMetadataValueList = new LinkedList<>(); + RelationshipType relationshipType = relationship.getRelationshipType(); + HashMap hashMaps; + String relationName = ""; + Item otherItem = null; + int place = 0; + if (StringUtils.equals(relationshipType.getLeftType().getLabel(), entityType)) { + hashMaps = virtualMetadataPopulator.getMap().get(relationshipType.getLeftLabel()); + otherItem = relationship.getRightItem(); + relationName = relationship.getRelationshipType().getLeftLabel(); + place = relationship.getLeftPlace(); + } else if (StringUtils.equals(relationshipType.getRightType().getLabel(), entityType)) { + hashMaps = virtualMetadataPopulator.getMap().get(relationshipType.getRightLabel()); + otherItem = relationship.getLeftItem(); + relationName = relationship.getRelationshipType().getRightLabel(); + place = relationship.getRightPlace(); + } else { + //No virtual metadata can be created + return resultingMetadataValueList; + } + + if (hashMaps != null && enableVirtualMetadata) { + resultingMetadataValueList.addAll(handleRelationshipTypeMetadataMapping(context, item, hashMaps, + otherItem, relationName, + relationship.getID(), place)); + } + RelationshipMetadataValue relationMetadataFromOtherItem = + getRelationMetadataFromOtherItem(context, otherItem, relationName, relationship.getID(), place); + if (relationMetadataFromOtherItem != null) { + resultingMetadataValueList.add(relationMetadataFromOtherItem); + } + return resultingMetadataValueList; + } + + //This method will retrieve a list of RelationshipMetadataValue objects based on the config passed along in the + //hashmaps parameter. The beans will be used to retrieve the values for the RelationshipMetadataValue objects + //and the keys of the hashmap will be used to construct the RelationshipMetadataValue object. + private List handleRelationshipTypeMetadataMapping(Context context, Item item, + HashMap hashMaps, + Item otherItem, String relationName, + Integer relationshipId, int place) + throws SQLException { + List resultingMetadataValueList = new LinkedList<>(); + for (Map.Entry entry : hashMaps.entrySet()) { + String key = entry.getKey(); + VirtualMetadataConfiguration virtualBean = entry.getValue(); + + for (String value : virtualBean.getValues(context, otherItem)) { + RelationshipMetadataValue metadataValue = constructMetadataValue(context, key); + if (metadataValue != null) { + metadataValue = constructResultingMetadataValue(item, value, metadataValue, relationshipId); + metadataValue.setUseForPlace(virtualBean.getUseForPlace()); + metadataValue.setPlace(place); + if (StringUtils.isNotBlank(metadataValue.getValue())) { + resultingMetadataValueList.add(metadataValue); + } + } + } + } + return resultingMetadataValueList; + } + + private RelationshipMetadataValue getRelationMetadataFromOtherItem(Context context, Item otherItem, + String relationName, + Integer relationshipId, int place) { + RelationshipMetadataValue metadataValue = constructMetadataValue(context, + MetadataSchemaEnum.RELATION + .getName() + "." + relationName); + if (metadataValue != null) { + metadataValue.setAuthority(Constants.VIRTUAL_AUTHORITY_PREFIX + relationshipId); + metadataValue.setValue(otherItem.getID().toString()); + metadataValue.setPlace(place); + return metadataValue; + } + return null; + } + + private String getEntityTypeStringFromMetadata(List list) { + for (MetadataValue mdv : list) { + if (StringUtils.equals(mdv.getMetadataField().getMetadataSchema().getName(), + "relationship") + && StringUtils.equals(mdv.getMetadataField().getElement(), + "type")) { + + return mdv.getValue(); + } + } + return null; + } + + private RelationshipMetadataValue constructResultingMetadataValue(Item item, String value, + RelationshipMetadataValue metadataValue, + Integer relationshipId) { + metadataValue.setValue(value); + metadataValue.setAuthority(Constants.VIRTUAL_AUTHORITY_PREFIX + relationshipId); + metadataValue.setConfidence(-1); + metadataValue.setDSpaceObject(item); + return metadataValue; + } + + //This method will construct a RelationshipMetadataValue object with proper schema, element and qualifier based + //on the key String parameter passed along to it + private RelationshipMetadataValue constructMetadataValue(Context context, String key) { + String[] splittedKey = key.split("\\."); + RelationshipMetadataValue metadataValue = new RelationshipMetadataValue(); + String metadataSchema = splittedKey.length > 0 ? splittedKey[0] : null; + String metadataElement = splittedKey.length > 1 ? splittedKey[1] : null; + String metadataQualifier = splittedKey.length > 2 ? splittedKey[2] : null; + MetadataField metadataField = null; + try { + metadataField = metadataFieldService + .findByElement(context, metadataSchema, metadataElement, metadataQualifier); + } catch (SQLException e) { + log.error("Could not find element with MetadataSchema: " + metadataSchema + + ", MetadataElement: " + metadataElement + " and MetadataQualifier: " + metadataQualifier, e); + return null; + } + if (metadataField == null) { + log.error("A MetadataValue was attempted to construct with MetadataField for parameters: " + + "metadataschema: {}, metadataelement: {}, metadataqualifier: {}", + metadataSchema, metadataElement, metadataQualifier); + return null; + } + metadataValue.setMetadataField(metadataField); + metadataValue.setLanguage(Item.ANY); + return metadataValue; + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataSchema.java b/dspace-api/src/main/java/org/dspace/content/MetadataSchema.java index 511cda3f7b..96bef0fa2c 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataSchema.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataSchema.java @@ -39,10 +39,6 @@ import org.hibernate.proxy.HibernateProxyHelper; @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @Table(name = "metadataschemaregistry") public class MetadataSchema implements ReloadableEntity { - /** - * Short Name of built-in Dublin Core schema. - */ - public static final String DC_SCHEMA = "dc"; @Id @Column(name = "metadata_schema_id") diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataSchemaEnum.java b/dspace-api/src/main/java/org/dspace/content/MetadataSchemaEnum.java new file mode 100644 index 0000000000..cac4485ec9 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/MetadataSchemaEnum.java @@ -0,0 +1,39 @@ +/** + * 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; + +/** + * This is an enum that holds track of a few special MetadataSchema types. + * It is important to note that this list is not exhaustive for the MetadataSchema + * types and different MetadataSchema can easily be made. + * These MetadataSchema objects are simply required. + */ +public enum MetadataSchemaEnum { + DC("dc"), RELATION("relation"); + + /** + * The String representation of the MetadataSchemaEnum + */ + private String name; + + /** + * Default constructor with the name parameter + * @param name The name parameter + */ + MetadataSchemaEnum(String name) { + this.name = name; + } + + /** + * Generic getter for the String representation of the enum object + * @return The name of the enum object + */ + public String getName() { + return name; + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/Relationship.java b/dspace-api/src/main/java/org/dspace/content/Relationship.java new file mode 100644 index 0000000000..9c0e07bbcb --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/Relationship.java @@ -0,0 +1,180 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; + +import org.dspace.core.Context; +import org.dspace.core.ReloadableEntity; + +/** + * This class represents a relationship + * It has a leftItem and a rightItem which are both DSpaceObjects + * that have a specified RelationshipType that links them together + * It also has a left and right place column that works just like a normal DSpace metadata place column + */ +@Entity +@Table(name = "relationship") +public class Relationship implements ReloadableEntity { + + /** + * The Integer ID field for this object + * This is automatically generated + */ + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "relationship_id_seq") + @SequenceGenerator(name = "relationship_id_seq", sequenceName = "relationship_id_seq", allocationSize = 1) + @Column(name = "id", unique = true, nullable = false, insertable = true, updatable = false) + protected Integer id; + + /** + * The leftItem property for the Relationship object. + * This leftItem is a DSpaceObject and is stored as an ID + */ + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "left_id", nullable = false) + private Item leftItem; + + /** + * The relationshipType property for this Relationship object + * This is stored as an ID in the database + */ + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "type_id", nullable = false) + private RelationshipType relationshipType; + + /** + * The rightItem property for the Relationship object. + * This rightItem is a DSpaceObject and is stored as an ID + */ + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "right_id", nullable = false) + private Item rightItem; + + /** + * An Integer to describe the left place for this relationship + */ + @Column(name = "left_place") + private int leftPlace; + + /** + * An Integer to describe the right place for this relationship + */ + @Column(name = "right_place") + private int rightPlace; + + /** + * Protected constructor, create object using: + * {@link org.dspace.content.service.RelationshipService#create(Context)} } + */ + protected Relationship() {} + /** + * Standard setter for the ID field + * @param id The ID to be set + */ + public void setId(Integer id) { + this.id = id; + } + + /** + * Standard getter for the leftItem field + * @return The leftItem Item object in this relationship + */ + public Item getLeftItem() { + return leftItem; + } + + /** + * Standard setter for the leftItem field + * @param leftItem The leftItem Item object that the leftItem field should be set to + */ + public void setLeftItem(Item leftItem) { + this.leftItem = leftItem; + } + + /** + * Standard getter for the relationshipType field + * @return The relationshipType RelationshipType object in this relationship + */ + public RelationshipType getRelationshipType() { + return relationshipType; + } + + /** + * Standard setter for the relationshipType field for the Relationship + * @param relationshipType The relationshipType that will be set in this Relationship + */ + public void setRelationshipType(RelationshipType relationshipType) { + this.relationshipType = relationshipType; + } + + /** + * Standard getter for the rightItem Item object in this Relationship + * @return the rightItem Item object + */ + public Item getRightItem() { + return rightItem; + } + + /** + * Standard setter for the rightItem Item object in this Relationship + * @param rightItem The rightItem Item object that will be used in this relationship + */ + public void setRightItem(Item rightItem) { + this.rightItem = rightItem; + } + + /** + * Standard getter for the leftPlace Integer in this Relationship + * @return The leftPlace integer for this relationship + */ + public int getLeftPlace() { + return leftPlace; + } + + /** + * Standard setter for the leftPlace Integer in this Relationship + * @param leftPlace the leftPlace Integer that will be used in this relationship + */ + public void setLeftPlace(int leftPlace) { + this.leftPlace = leftPlace; + } + + /** + * Standard getter for the rightPlace Integer in this Relationship + * @return the rightPlace integer for this relationship + */ + public int getRightPlace() { + return rightPlace; + } + + /** + * Standard setter for the rightPlace Integer in this Relationship + * @param rightPlace the rightPlace Integer that will be used in this relationship + */ + public void setRightPlace(int rightPlace) { + this.rightPlace = rightPlace; + } + + /** + * Standard getter for the ID for this Relationship + * @return The ID of this relationship + */ + public Integer getID() { + return id; + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipMetadataValue.java b/dspace-api/src/main/java/org/dspace/content/RelationshipMetadataValue.java new file mode 100644 index 0000000000..88d2e38beb --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/RelationshipMetadataValue.java @@ -0,0 +1,60 @@ +/** + * 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; + +/** + * This class is used as a representation of MetadataValues for the MetadataValues that are derived from the + * Relationships that the item has. This includes the useForPlace property which we'll have to use to determine + * whether these Values should be counted for place calculation on both the native MetadataValues and the + * Relationship's place attributes. + */ +public class RelationshipMetadataValue extends MetadataValue { + + /** + * This property determines whether this RelationshipMetadataValue should be used in place calculation or not + */ + private boolean useForPlace; + + /** + * This property determines whether this RelationshipMetadataValue should be used in place calculation or not. + * This is retrieved from Spring configuration when constructing RelationshipMetadataValues. This Spring + * configuration is located in the core-services.xml configuration file. + * Putting this property on true will imply that we're now mixing plain-text metadatavalues with the + * metadatavalues that are constructed through Relationships with regards to the place attribute. + * For example, currently the RelationshipMetadataValue dc.contributor.author that is constructed through a + * Relationship for a Publication will have its useForPlace set to true. This means that the place + * calculation will take both these RelationshipMetadataValues into account together with the normal + * plain text metadatavalues. + * On the other hand, the journal name, volume and issue number which are constructed through a + * Relationship from a Publication to a journal issue will have its useForPlace set to false. + * This would typically be set to false for any singular metadata, and for any relationship where no mixing + * with plain text variables is applicable + */ + public boolean isUseForPlace() { + return useForPlace; + } + + public void setUseForPlace(boolean useForPlace) { + this.useForPlace = useForPlace; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (! (obj instanceof RelationshipMetadataValue)) { + return false; + } + final RelationshipMetadataValue other = (RelationshipMetadataValue) obj; + if (this.isUseForPlace() != other.isUseForPlace()) { + return false; + } + return super.equals(obj); + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java new file mode 100644 index 0000000000..7e4827ea06 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java @@ -0,0 +1,414 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content; + +import java.sql.SQLException; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.dao.RelationshipDAO; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.RelationshipService; +import org.dspace.content.service.RelationshipTypeService; +import org.dspace.content.virtual.VirtualMetadataPopulator; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +public class RelationshipServiceImpl implements RelationshipService { + + private static final Logger log = LogManager.getLogger(); + + @Autowired(required = true) + protected RelationshipDAO relationshipDAO; + + @Autowired(required = true) + protected AuthorizeService authorizeService; + + @Autowired(required = true) + protected ItemService itemService; + + @Autowired(required = true) + protected RelationshipTypeService relationshipTypeService; + + @Autowired + private VirtualMetadataPopulator virtualMetadataPopulator; + + @Override + public Relationship create(Context context) throws SQLException, AuthorizeException { + if (!authorizeService.isAdmin(context)) { + throw new AuthorizeException( + "Only administrators can modify relationship"); + } + return relationshipDAO.create(context, new Relationship()); + } + + @Override + public Relationship create(Context c, Item leftItem, Item rightItem, RelationshipType relationshipType, + int leftPlace, int rightPlace) throws AuthorizeException, SQLException { + Relationship relationship = new Relationship(); + relationship.setLeftItem(leftItem); + relationship.setRightItem(rightItem); + relationship.setRelationshipType(relationshipType); + relationship.setLeftPlace(leftPlace); + relationship.setRightPlace(rightPlace); + return create(c, relationship); + } + + @Override + public Relationship create(Context context, Relationship relationship) throws SQLException, AuthorizeException { + if (isRelationshipValidToCreate(context, relationship)) { + if (authorizeService.authorizeActionBoolean(context, relationship.getLeftItem(), Constants.WRITE) || + authorizeService.authorizeActionBoolean(context, relationship.getRightItem(), Constants.WRITE)) { + updatePlaceInRelationship(context, relationship, true); + return relationshipDAO.create(context, relationship); + } else { + throw new AuthorizeException( + "You do not have write rights on this relationship's items"); + } + + } else { + throw new IllegalArgumentException("The relationship given was not valid"); + } + } + + @Override + public void updatePlaceInRelationship(Context context, Relationship relationship, boolean isCreation) + throws SQLException, AuthorizeException { + Item leftItem = relationship.getLeftItem(); + List leftRelationships = findByItemAndRelationshipType(context, + leftItem, + relationship.getRelationshipType(), true); + Item rightItem = relationship.getRightItem(); + List rightRelationships = findByItemAndRelationshipType(context, + rightItem, + relationship.getRelationshipType(), + false); + + context.turnOffAuthorisationSystem(); + //If useForPlace for the leftlabel is false for the relationshipType, + // we need to sort the relationships here based on leftplace. + if (!virtualMetadataPopulator.isUseForPlaceTrueForRelationshipType(relationship.getRelationshipType(), true)) { + if (!leftRelationships.isEmpty()) { + leftRelationships.sort(Comparator.comparingInt(Relationship::getLeftPlace)); + for (int i = 0; i < leftRelationships.size(); i++) { + leftRelationships.get(i).setLeftPlace(i); + } + relationship.setLeftPlace(leftRelationships.size()); + } else { + relationship.setLeftPlace(0); + } + } else { + updateItem(context, leftItem); + + } + + //If useForPlace for the rightLabel is false for the relationshipType, + // we need to sort the relationships here based on the rightplace. + if (!virtualMetadataPopulator.isUseForPlaceTrueForRelationshipType(relationship.getRelationshipType(), false)) { + if (!rightRelationships.isEmpty()) { + rightRelationships.sort(Comparator.comparingInt(Relationship::getRightPlace)); + for (int i = 0; i < rightRelationships.size(); i++) { + rightRelationships.get(i).setRightPlace(i); + } + relationship.setRightPlace(rightRelationships.size()); + } else { + relationship.setRightPlace(0); + } + + } else { + updateItem(context, rightItem); + + } + + if (isCreation) { + handleCreationPlaces(context, relationship); + } + context.restoreAuthSystemState(); + + } + + @Override + public void updateItem(Context context, Item relatedItem) + throws SQLException, AuthorizeException { + relatedItem.setMetadataModified(); + itemService.update(context, relatedItem); + } + + + //Sets the places for the Relationship properly if the updatePlaceInRelationship was called for a new creation + //of this Relationship + private void handleCreationPlaces(Context context, Relationship relationship) throws SQLException { + List leftRelationships; + List rightRelationships; + leftRelationships = findByItemAndRelationshipType(context, + relationship.getLeftItem(), + relationship.getRelationshipType(), true); + rightRelationships = findByItemAndRelationshipType(context, + relationship.getRightItem(), + relationship.getRelationshipType(), + false); + leftRelationships.sort((o1, o2) -> o2.getLeftPlace() - o1.getLeftPlace()); + rightRelationships.sort((o1, o2) -> o2.getRightPlace() - o1.getRightPlace()); + + if (!leftRelationships.isEmpty()) { + relationship.setLeftPlace(leftRelationships.get(0).getLeftPlace() + 1); + } else { + relationship.setLeftPlace(0); + } + + if (!rightRelationships.isEmpty()) { + relationship.setRightPlace(rightRelationships.get(0).getRightPlace() + 1); + } else { + relationship.setRightPlace(0); + } + } + + @Override + public int findLeftPlaceByLeftItem(Context context, Item item) throws SQLException { + return relationshipDAO.findLeftPlaceByLeftItem(context, item); + } + + @Override + public int findRightPlaceByRightItem(Context context, Item item) throws SQLException { + return relationshipDAO.findRightPlaceByRightItem(context, item); + } + + private boolean isRelationshipValidToCreate(Context context, Relationship relationship) throws SQLException { + RelationshipType relationshipType = relationship.getRelationshipType(); + + if (!verifyEntityTypes(relationship.getLeftItem(), relationshipType.getLeftType())) { + log.warn("The relationship has been deemed invalid since the leftItem" + + " and leftType do no match on entityType"); + logRelationshipTypeDetailsForError(relationshipType); + return false; + } + if (!verifyEntityTypes(relationship.getRightItem(), relationshipType.getRightType())) { + log.warn("The relationship has been deemed invalid since the rightItem" + + " and rightType do no match on entityType"); + logRelationshipTypeDetailsForError(relationshipType); + return false; + } + if (!verifyMaxCardinality(context, relationship.getLeftItem(), + relationshipType.getLeftMaxCardinality(), relationshipType)) { + log.warn("The relationship has been deemed invalid since the left item has more" + + " relationships than the left max cardinality allows after we'd store this relationship"); + logRelationshipTypeDetailsForError(relationshipType); + return false; + } + if (!verifyMaxCardinality(context, relationship.getRightItem(), + relationshipType.getRightMaxCardinality(), relationshipType)) { + log.warn("The relationship has been deemed invalid since the right item has more" + + " relationships than the right max cardinality allows after we'd store this relationship"); + logRelationshipTypeDetailsForError(relationshipType); + return false; + } + return true; + } + + private void logRelationshipTypeDetailsForError(RelationshipType relationshipType) { + log.warn("The relationshipType's ID is: " + relationshipType.getID()); + log.warn("The relationshipType's left label is: " + relationshipType.getLeftLabel()); + log.warn("The relationshipType's right label is: " + relationshipType.getRightLabel()); + log.warn("The relationshipType's left entityType label is: " + relationshipType.getLeftType().getLabel()); + log.warn("The relationshipType's right entityType label is: " + relationshipType.getRightType().getLabel()); + log.warn("The relationshipType's left min cardinality is: " + relationshipType.getLeftMinCardinality()); + log.warn("The relationshipType's left max cardinality is: " + relationshipType.getLeftMaxCardinality()); + log.warn("The relationshipType's right min cardinality is: " + relationshipType.getRightMinCardinality()); + log.warn("The relationshipType's right max cardinality is: " + relationshipType.getRightMaxCardinality()); + } + + private boolean verifyMaxCardinality(Context context, Item itemToProcess, + Integer maxCardinality, + RelationshipType relationshipType) throws SQLException { + List rightRelationships = findByItemAndRelationshipType(context, itemToProcess, relationshipType, + false); + if (maxCardinality != null && rightRelationships.size() >= maxCardinality) { + return false; + } + return true; + } + + private boolean verifyEntityTypes(Item itemToProcess, EntityType entityTypeToProcess) { + List list = itemService.getMetadata(itemToProcess, "relationship", "type", null, Item.ANY); + if (list.isEmpty()) { + return false; + } + String leftEntityType = list.get(0).getValue(); + return StringUtils.equals(leftEntityType, entityTypeToProcess.getLabel()); + } + + public Relationship find(Context context, int id) throws SQLException { + Relationship relationship = relationshipDAO.findByID(context, Relationship.class, id); + return relationship; + } + + @Override + public List findByItem(Context context, Item item) throws SQLException { + + List list = relationshipDAO.findByItem(context, item); + + list.sort((o1, o2) -> { + int relationshipType = o1.getRelationshipType().getLeftLabel() + .compareTo(o2.getRelationshipType().getLeftLabel()); + if (relationshipType != 0) { + return relationshipType; + } else { + if (o1.getLeftItem() == item) { + return o1.getLeftPlace() - o2.getLeftPlace(); + } else { + return o1.getRightPlace() - o2.getRightPlace(); + } + } + }); + return list; + } + + @Override + public List findAll(Context context) throws SQLException { + return relationshipDAO.findAll(context, Relationship.class); + } + + @Override + public void update(Context context, Relationship relationship) throws SQLException, AuthorizeException { + update(context, Collections.singletonList(relationship)); + + } + + @Override + public void update(Context context, List relationships) throws SQLException, AuthorizeException { + if (CollectionUtils.isNotEmpty(relationships)) { + for (Relationship relationship : relationships) { + if (authorizeService.authorizeActionBoolean(context, relationship.getLeftItem(), Constants.WRITE) || + authorizeService.authorizeActionBoolean(context, relationship.getRightItem(), Constants.WRITE)) { + if (isRelationshipValidToCreate(context, relationship)) { + relationshipDAO.save(context, relationship); + } + } else { + throw new AuthorizeException("You do not have write rights on this relationship's items"); + } + } + } + } + + @Override + public void delete(Context context, Relationship relationship) throws SQLException, AuthorizeException { + if (isRelationshipValidToDelete(context, relationship)) { + // To delete a relationship, a user must have WRITE permissions on one of the related Items + if (authorizeService.authorizeActionBoolean(context, relationship.getLeftItem(), Constants.WRITE) || + authorizeService.authorizeActionBoolean(context, relationship.getRightItem(), Constants.WRITE)) { + relationshipDAO.delete(context, relationship); + updatePlaceInRelationship(context, relationship, false); + } else { + throw new AuthorizeException( + "You do not have write rights on this relationship's items"); + } + + } else { + throw new IllegalArgumentException("The relationship given was not valid"); + } + } + + private boolean isRelationshipValidToDelete(Context context, Relationship relationship) throws SQLException { + if (relationship == null) { + log.warn("The relationship has been deemed invalid since the relation was null"); + return false; + } + if (relationship.getID() == null) { + log.warn("The relationship has been deemed invalid since the ID" + + " off the given relationship was null"); + return false; + } + if (this.find(context, relationship.getID()) == null) { + log.warn("The relationship has been deemed invalid since the relationship" + + " is not present in the DB with the current ID"); + logRelationshipTypeDetailsForError(relationship.getRelationshipType()); + return false; + } + if (!checkMinCardinality(context, relationship.getLeftItem(), + relationship, relationship.getRelationshipType().getLeftMinCardinality(), true)) { + log.warn("The relationship has been deemed invalid since the leftMinCardinality" + + " constraint would be violated upon deletion"); + logRelationshipTypeDetailsForError(relationship.getRelationshipType()); + return false; + } + + if (!checkMinCardinality(context, relationship.getRightItem(), + relationship, relationship.getRelationshipType().getRightMinCardinality(), false)) { + log.warn("The relationship has been deemed invalid since the rightMinCardinality" + + " constraint would be violated upon deletion"); + logRelationshipTypeDetailsForError(relationship.getRelationshipType()); + return false; + } + return true; + } + + private boolean checkMinCardinality(Context context, Item item, + Relationship relationship, + Integer minCardinality, boolean isLeft) throws SQLException { + List list = this + .findByItemAndRelationshipType(context, item, relationship.getRelationshipType(), isLeft); + if (minCardinality != null && !(list.size() > minCardinality)) { + return false; + } + return true; + } + + public List findByItemAndRelationshipType(Context context, Item item, + RelationshipType relationshipType, boolean isLeft) + + throws SQLException { + List list = this.findByItem(context, item); + List listToReturn = new LinkedList<>(); + for (Relationship relationship : list) { + if (isLeft) { + if (StringUtils + .equals(relationship.getRelationshipType().getLeftLabel(), relationshipType.getLeftLabel())) { + listToReturn.add(relationship); + } + } else { + if (StringUtils + .equals(relationship.getRelationshipType().getRightLabel(), relationshipType.getRightLabel())) { + listToReturn.add(relationship); + } + } + } + return listToReturn; + } + + @Override + public List findByItemAndRelationshipType(Context context, Item item, + RelationshipType relationshipType) + + throws SQLException { + List list = this.findByItem(context, item); + List listToReturn = new LinkedList<>(); + for (Relationship relationship : list) { + if (relationship.getRelationshipType().equals(relationshipType)) { + listToReturn.add(relationship); + } + } + return listToReturn; + } + + @Override + public List findByRelationshipType(Context context, RelationshipType relationshipType) + throws SQLException { + return relationshipDAO.findByRelationshipType(context, relationshipType); + } + + +} diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipType.java b/dspace-api/src/main/java/org/dspace/content/RelationshipType.java new file mode 100644 index 0000000000..76314478fb --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/RelationshipType.java @@ -0,0 +1,254 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; + +import org.dspace.core.Context; +import org.dspace.core.ReloadableEntity; + +/** + * Class representing a RelationshipType + * This class contains an Integer ID that will be the unique value and primary key in the database. + * This key is automatically generated + * It also has a leftType and rightType EntityType that describes the relationshipType together with a leftLabel and + * rightLabel. + * The cardinality properties describe how many of each relations this relationshipType can support + */ +@Entity +@Table(name = "relationship_type") +public class RelationshipType implements ReloadableEntity { + + /** + * The Integer ID used as a primary key for this database object. + * This is generated by a sequence + */ + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "relationship_type_id_seq") + @SequenceGenerator(name = "relationship_type_id_seq", sequenceName = "relationship_type_id_seq", allocationSize = 1) + @Column(name = "id", unique = true, nullable = false, insertable = true, updatable = false) + protected Integer id; + + /** + * The leftType EntityType field for the relationshipType + * This is stored as an ID and cannot be null + */ + @ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST}) + @JoinColumn(name = "left_type", nullable = false) + private EntityType leftType; + + /** + * The rightType EntityType field for the relationshipType + * This is stored as an ID and cannot be null + */ + @ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST}) + @JoinColumn(name = "right_type", nullable = false) + private EntityType rightType; + + /** + * The leftLabel String field for the relationshipType + * This is stored as a String and cannot be null + * This is a textual representation of the name of the relationship that this RelationshipType is connected to + */ + @Column(name = "left_label", nullable = false) + private String leftLabel; + + /** + * The rightLabel String field for the relationshipType + * This is stored as a String and cannot be null + * This is a textual representation of the name of the relationship that this RelationshipType is connected to + */ + @Column(name = "right_label", nullable = false) + private String rightLabel; + + /** + * The minimum amount of relations for the leftItem that need to be present at all times + * This is stored as an Integer + */ + @Column(name = "left_min_cardinality") + private Integer leftMinCardinality; + + /** + * The maximum amount of relations for the leftItem that can to be present at all times + * This is stored as an Integer + */ + @Column(name = "left_max_cardinality") + private Integer leftMaxCardinality; + + /** + * The minimum amount of relations for the rightItem that need to be present at all times + */ + @Column(name = "right_min_cardinality") + private Integer rightMinCardinality; + + /** + * Tha maximum amount of relations for the rightItem that can be present at all times + */ + @Column(name = "right_max_cardinality") + private Integer rightMaxCardinality; + + /** + * Protected constructor, create object using: + * {@link org.dspace.content.service.RelationshipTypeService#create(Context)} } + */ + protected RelationshipType() {} + + /** + * Standard getter for the ID of this RelationshipType + * @param id The ID that this RelationshipType should receive + */ + public void setId(Integer id) { + this.id = id; + } + + /** + * Standard getter for The leftType EntityType for this RelationshipType + * @return The leftType EntityType of this RelationshipType + */ + public EntityType getLeftType() { + return leftType; + } + + /** + * Standard setter for the leftType EntityType for this RelationshipType + * @param leftType The leftType EntityType that this RelationshipType should receive + */ + public void setLeftType(EntityType leftType) { + this.leftType = leftType; + } + + /** + * Standard getter for The rightType EntityType for this RelationshipType + * @return The rightType EntityType of this RelationshipType + */ + public EntityType getRightType() { + return rightType; + } + + /** + * Standard setter for the rightType EntityType for this RelationshipType + * @param rightType The rightType EntityType that this RelationshipType should receive + */ + public void setRightType(EntityType rightType) { + this.rightType = rightType; + } + + /** + * Standard getter for the leftLabel String for this RelationshipType + * @return The leftLabel String of this RelationshipType + */ + public String getLeftLabel() { + return leftLabel; + } + + /** + * Standard setter for the leftLabel String for this RelationshipType + * @param leftLabel The leftLabel String that this RelationshipType should receive + */ + public void setLeftLabel(String leftLabel) { + this.leftLabel = leftLabel; + } + + /** + * Standard getter for the rightLabel String for this RelationshipType + * @return The rightLabel String of this RelationshipType + */ + public String getRightLabel() { + return rightLabel; + } + + /** + * Standard setter for the rightLabel String for this RelationshipType + * @param rightLabel The rightLabel String that this RelationshipType should receive + */ + public void setRightLabel(String rightLabel) { + this.rightLabel = rightLabel; + } + + /** + * Standard getter for the leftMinCardinality Integer for this RelationshipType + * @return the leftMinCardinality Integer of this RelationshipType + */ + public Integer getLeftMinCardinality() { + return leftMinCardinality; + } + + /** + * Standard setter for the leftMinCardinality Integer for this RelationshipType + * @param leftMinCardinality The leftMinCardinality Integer that this RelationshipType should recieve + */ + public void setLeftMinCardinality(Integer leftMinCardinality) { + this.leftMinCardinality = leftMinCardinality; + } + + /** + * Standard getter for the leftMaxCardinality Integer for this RelationshipType + * @return the leftMaxCardinality Integer of this RelationshipType + */ + public Integer getLeftMaxCardinality() { + return leftMaxCardinality; + } + + /** + * Standard setter for the leftMaxCardinality Integer for this RelationshipType + * @param leftMaxCardinality The leftMaxCardinality Integer that this RelationshipType should recieve + */ + public void setLeftMaxCardinality(Integer leftMaxCardinality) { + this.leftMaxCardinality = leftMaxCardinality; + } + + /** + * Standard getter for the rightMinCardinality Integer for this RelationshipType + * @return the rightMinCardinality Integer of this RelationshipType + */ + public Integer getRightMinCardinality() { + return rightMinCardinality; + } + + /** + * Standard setter for the rightMinCardinality Integer for this RelationshipType + * @param rightMinCardinality The rightMinCardinality Integer that this RelationshipType should recieve + */ + public void setRightMinCardinality(Integer rightMinCardinality) { + this.rightMinCardinality = rightMinCardinality; + } + + /** + * Standard getter for the rightMaxCardinality Integer for this RelationshipType + * @return the rightMaxCardinality Integer of this RelationshipType + */ + public Integer getRightMaxCardinality() { + return rightMaxCardinality; + } + + /** + * Standard setter for the rightMaxCardinality Integer for this RelationshipType + * @param rightMaxCardinality The rightMaxCardinality Integer that this RelationshipType should recieve + */ + public void setRightMaxCardinality(Integer rightMaxCardinality) { + this.rightMaxCardinality = rightMaxCardinality; + } + + /** + * Standard getter for the ID of this RelationshipType + * @return The ID of this RelationshipType + */ + public Integer getID() { + return id; + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipTypeServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/RelationshipTypeServiceImpl.java new file mode 100644 index 0000000000..1900de8777 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/RelationshipTypeServiceImpl.java @@ -0,0 +1,123 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content; + +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.collections.CollectionUtils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.dao.RelationshipTypeDAO; +import org.dspace.content.service.RelationshipTypeService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +public class RelationshipTypeServiceImpl implements RelationshipTypeService { + + @Autowired(required = true) + protected RelationshipTypeDAO relationshipTypeDAO; + + @Autowired(required = true) + protected AuthorizeService authorizeService; + + @Override + public RelationshipType create(Context context) throws SQLException, AuthorizeException { + if (!authorizeService.isAdmin(context)) { + throw new AuthorizeException( + "Only administrators can modify relationshipType"); + } + return relationshipTypeDAO.create(context, new RelationshipType()); + } + + @Override + public RelationshipType create(Context context, RelationshipType relationshipType) + throws SQLException, AuthorizeException { + if (!authorizeService.isAdmin(context)) { + throw new AuthorizeException( + "Only administrators can modify relationshipType"); + } + return relationshipTypeDAO.create(context, relationshipType); + } + + @Override + public RelationshipType findbyTypesAndLabels(Context context,EntityType leftType,EntityType rightType, + String leftLabel,String rightLabel) throws SQLException { + return relationshipTypeDAO.findByTypesAndLabels(context, leftType, rightType, leftLabel, rightLabel); + } + + @Override + public List findAll(Context context) throws SQLException { + return relationshipTypeDAO.findAll(context, RelationshipType.class); + } + + @Override + public List findByLeftOrRightLabel(Context context, String label) throws SQLException { + return relationshipTypeDAO.findByLeftOrRightLabel(context, label); + } + + @Override + public List findByEntityType(Context context, EntityType entityType) throws SQLException { + return relationshipTypeDAO.findByEntityType(context, entityType); + } + + @Override + public RelationshipType create(Context context, EntityType leftEntityType, EntityType rightEntityType, + String leftLabel, String rightLabel, Integer leftCardinalityMinInteger, + Integer leftCardinalityMaxInteger, Integer rightCardinalityMinInteger, + Integer rightCardinalityMaxInteger) + throws SQLException, AuthorizeException { + RelationshipType relationshipType = new RelationshipType(); + relationshipType.setLeftType(leftEntityType); + relationshipType.setRightType(rightEntityType); + relationshipType.setLeftLabel(leftLabel); + relationshipType.setRightLabel(rightLabel); + relationshipType.setLeftMinCardinality(leftCardinalityMinInteger); + relationshipType.setLeftMaxCardinality(leftCardinalityMaxInteger); + relationshipType.setRightMinCardinality(rightCardinalityMinInteger); + relationshipType.setRightMaxCardinality(rightCardinalityMaxInteger); + return create(context, relationshipType); + } + + @Override + public RelationshipType find(Context context,int id) throws SQLException { + return relationshipTypeDAO.findByID(context, RelationshipType.class, id); + } + + @Override + public void update(Context context,RelationshipType relationshipType) throws SQLException, AuthorizeException { + update(context,Collections.singletonList(relationshipType)); + } + + @Override + public void update(Context context,List relationshipTypes) + throws SQLException, AuthorizeException { + if (CollectionUtils.isNotEmpty(relationshipTypes)) { + // Check authorisation - only administrators can change formats + if (!authorizeService.isAdmin(context)) { + throw new AuthorizeException( + "Only administrators can modify RelationshipType"); + } + + for (RelationshipType relationshipType : relationshipTypes) { + relationshipTypeDAO.save(context, relationshipType); + } + } + + } + + @Override + public void delete(Context context,RelationshipType relationshipType) throws SQLException, AuthorizeException { + if (!authorizeService.isAdmin(context)) { + throw new AuthorizeException( + "Only administrators can delete entityType"); + } + relationshipTypeDAO.delete(context, relationshipType); + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/OAIDCIngestionCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/OAIDCIngestionCrosswalk.java index 98b4df7d8c..10bd5ce6fa 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/OAIDCIngestionCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/OAIDCIngestionCrosswalk.java @@ -15,7 +15,7 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.MetadataField; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; @@ -67,7 +67,8 @@ public class OAIDCIngestionCrosswalk lang = element.getAttributeValue("lang"); } MetadataField metadataField = metadataValidator - .checkMetadata(context, MetadataSchema.DC_SCHEMA, element.getName(), null, createMissingMetadataFields); + .checkMetadata(context, MetadataSchemaEnum.DC.getName(), + element.getName(), null, createMissingMetadataFields); itemService.addMetadata(context, item, metadataField, lang, element.getText()); } diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/OREDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/OREDisseminationCrosswalk.java index af9c64b858..f0e20cfffc 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/OREDisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/OREDisseminationCrosswalk.java @@ -22,7 +22,7 @@ import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.MetadataValue; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.ItemService; @@ -122,7 +122,7 @@ public class OREDisseminationCrosswalk Element aggLink; List uris = itemService - .getMetadata(item, MetadataSchema.DC_SCHEMA, "identifier", "uri", Item.ANY); + .getMetadata(item, MetadataSchemaEnum.DC.getName(), "identifier", "uri", Item.ANY); for (MetadataValue uri : uris) { aggLink = new Element("link", ATOM_NS); aggLink.setAttribute("rel", "alternate"); @@ -159,7 +159,8 @@ public class OREDisseminationCrosswalk // Information about the aggregation (item) itself Element aggTitle = new Element("title", ATOM_NS); - List titles = itemService.getMetadata(item, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY); + List titles = itemService.getMetadata(item, MetadataSchemaEnum.DC.getName(), + "title", null, Item.ANY); if (titles != null && titles.size() > 0) { aggTitle.addContent(titles.get(0).getValue()); } else { @@ -170,7 +171,7 @@ public class OREDisseminationCrosswalk Element aggAuthor; Element aggAuthorName; List authors = itemService - .getMetadata(item, MetadataSchema.DC_SCHEMA, "contributor", "author", Item.ANY); + .getMetadata(item, MetadataSchemaEnum.DC.getName(), "contributor", "author", Item.ANY); for (MetadataValue author : authors) { aggAuthor = new Element("author", ATOM_NS); aggAuthorName = new Element("name", ATOM_NS); diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/QDCCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/QDCCrosswalk.java index 630d1d45ef..edb3c27bef 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/QDCCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/QDCCrosswalk.java @@ -26,6 +26,7 @@ import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.MetadataField; import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.MetadataValue; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.ItemService; @@ -353,7 +354,7 @@ public class QDCCrosswalk extends SelfNamedPlugin // only complain about missing elements in the DC schema: if (elt == null) { - if (metadataField.getMetadataSchema().getName().equals(MetadataSchema.DC_SCHEMA)) { + if (metadataField.getMetadataSchema().getName().equals(MetadataSchemaEnum.DC.getName())) { log.warn("WARNING: " + myName + ": No QDC mapping for \"" + qdc + "\""); } } else { diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/SimpleDCDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/SimpleDCDisseminationCrosswalk.java index c9a9ddd900..22ec68070a 100644 --- a/dspace-api/src/main/java/org/dspace/content/crosswalk/SimpleDCDisseminationCrosswalk.java +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/SimpleDCDisseminationCrosswalk.java @@ -17,7 +17,7 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.MetadataField; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.MetadataValue; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.ItemService; @@ -101,7 +101,7 @@ public class SimpleDCDisseminationCrosswalk extends SelfNamedPlugin Item item = (Item) dso; List allDC = itemService - .getMetadata(item, MetadataSchema.DC_SCHEMA, Item.ANY, Item.ANY, Item.ANY); + .getMetadata(item, MetadataSchemaEnum.DC.getName(), Item.ANY, Item.ANY, Item.ANY); List dcl = new ArrayList(allDC.size()); diff --git a/dspace-api/src/main/java/org/dspace/content/dao/EntityTypeDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/EntityTypeDAO.java new file mode 100644 index 0000000000..4e8a5934dd --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/dao/EntityTypeDAO.java @@ -0,0 +1,35 @@ +/** + * 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.dao; + +import java.sql.SQLException; + +import org.dspace.content.EntityType; +import org.dspace.core.Context; +import org.dspace.core.GenericDAO; + +/** + * Database Access Object Interface class for the EntityType object + * The implementation of this class is responsible for all database calls for the EntityType object and is autowired by + * spring + * This class should only be accessed from a single service and should never be exposed outside of the API + */ +public interface EntityTypeDAO extends GenericDAO { + + /** + * This method returns the EntityType object that has the given entityType String + * as label + * @param context The relevant DSpace context + * @param entityType The entityType String that will be matched on to find + * the correct EntityType + * @return The EntityType object that has the entityType String as label + * @throws SQLException If something goes wrong + */ + public EntityType findByEntityType(Context context, String entityType) throws SQLException; + +} diff --git a/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java new file mode 100644 index 0000000000..5352e39680 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java @@ -0,0 +1,72 @@ +/** + * 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.dao; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.content.Item; +import org.dspace.content.Relationship; +import org.dspace.content.RelationshipType; +import org.dspace.core.Context; +import org.dspace.core.GenericDAO; + +/** + * Database Access Object Interface class for the Relationship object + * The implementation of this class is responsible for all + * database calls for the Relationship object and is autowired by spring + * This class should only be accessed from a single service and should never be exposed outside of the API + */ +public interface RelationshipDAO extends GenericDAO { + + /** + * This method returns a list of Relationship objects that have the given Item object + * as a leftItem or a rightItem + * @param context The relevant DSpace context + * @param item The item that should be either a leftItem or a rightItem of all + * the Relationship objects in the returned list + * @return The list of Relationship objects that contain either a left or a + * right item that is equal to the given item + * @throws SQLException If something goes wrong + */ + List findByItem(Context context,Item item) throws SQLException; + + /** + * This method returns the highest leftplace integer for all the relationships where this + * item is the leftitem so that we can set a proper leftplace attribute on the next relationship + * @param context The relevant DSpace context + * @param item The item to be matched on leftItem + * @return The integer for the highest leftPlace value for all the relatonship objects + * that have the given item as leftItem + * @throws SQLException If something goes wrong + */ + int findLeftPlaceByLeftItem(Context context,Item item) throws SQLException; + + /** + * This method returns the highest rightplace integer for all the relationships where this + * item is the rightitem so that we can set a proper rightplace attribute on the next relationship + * @param context The relevant DSpace context + * @param item The item to be matched on rightItem + * @return The integer for the highest rightPlace value for all the relatonship objects + * that have the given item as rightItem + * @throws SQLException If something goes wrong + */ + int findRightPlaceByRightItem(Context context,Item item) throws SQLException; + + /** + * This method returns a list of Relationship objects for the given RelationshipType object. + * It will construct a list of all Relationship objects that have the given RelationshipType object + * as the relationshipType property + * @param context The relevant DSpace context + * @param relationshipType The RelationshipType object to be checked on + * @return A list of Relationship objects that have the given RelationshipType object as the + * relationshipType property + * @throws SQLException If something goes wrong + */ + List findByRelationshipType(Context context, RelationshipType relationshipType) throws SQLException; +} diff --git a/dspace-api/src/main/java/org/dspace/content/dao/RelationshipTypeDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/RelationshipTypeDAO.java new file mode 100644 index 0000000000..d3be8f233b --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/dao/RelationshipTypeDAO.java @@ -0,0 +1,61 @@ +/** + * 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.dao; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.content.EntityType; +import org.dspace.content.RelationshipType; +import org.dspace.core.Context; +import org.dspace.core.GenericDAO; + +/** + * Database Access Object Interface class for the RelationshipType object + * The implementation of this class is responsible for all + * database calls for the RelationshipType object and is autowired by spring + * This class should only be accessed from a single service and should never be exposed outside of the API + */ +public interface RelationshipTypeDAO extends GenericDAO { + + /** + * This method is used to retrieve the RelationshipType object that has the same + * leftType, rightType, leftLabel and rightLabel as given in the parameters + * @param context The relevant DSpace context + * @param leftType The leftType EntityType object to be matched in the query + * @param rightType The rightType EntityType object to be matched in the query + * @param leftLabel The leftLabel String to be matched in the query + * @param rightLabel The rightLabel String to be matched in the query + * @return The RelationshipType object that matches all the given parameters + * @throws SQLException If something goes wrong + */ + RelationshipType findByTypesAndLabels(Context context, + EntityType leftType,EntityType rightType,String leftLabel,String rightLabel) + throws SQLException; + + /** + * This method will return a list of RelationshipType objects for which the given label is equal to + * either the leftLabel or rightLabel. + * @param context The relevant DSpace context + * @param label The label that will be used to check on + * @return A list of RelationshipType objects that have the given label as either the leftLabel or rightLabel + * @throws SQLException If something goes wrong + */ + List findByLeftOrRightLabel(Context context, String label) throws SQLException; + + /** + * This method will return a list of RelationshipType objects for which the given EntityType object is equal + * to the leftType or rightType + * @param context The relevant DSpace context + * @param entityType The EntityType object that will be used to check on + * @return The list of RelationshipType objects that have the given EntityType object + * as either a leftType or rightType + * @throws SQLException If something goes wrong + */ + List findByEntityType(Context context, EntityType entityType) throws SQLException; +} diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/EntityTypeDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/EntityTypeDAOImpl.java new file mode 100644 index 0000000000..b9c48c6711 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/EntityTypeDAOImpl.java @@ -0,0 +1,33 @@ +/** + * 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.dao.impl; + +import java.sql.SQLException; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.dspace.content.EntityType; +import org.dspace.content.EntityType_; +import org.dspace.content.dao.EntityTypeDAO; +import org.dspace.core.AbstractHibernateDAO; +import org.dspace.core.Context; + +public class EntityTypeDAOImpl extends AbstractHibernateDAO implements EntityTypeDAO { + + @Override + public EntityType findByEntityType(Context context, String entityType) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, EntityType.class); + Root entityTypeRoot = criteriaQuery.from(EntityType.class); + criteriaQuery.select(entityTypeRoot); + criteriaQuery.where(criteriaBuilder.equal(criteriaBuilder.upper(entityTypeRoot.get(EntityType_.label)), + entityType.toUpperCase())); + return uniqueResult(context, criteriaQuery, true, EntityType.class, -1, -1); + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java new file mode 100644 index 0000000000..2df8ed0a2e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipDAOImpl.java @@ -0,0 +1,83 @@ +/** + * 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.dao.impl; + +import java.sql.SQLException; +import java.util.List; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.dspace.content.Item; +import org.dspace.content.Relationship; +import org.dspace.content.RelationshipType; +import org.dspace.content.Relationship_; +import org.dspace.content.dao.RelationshipDAO; +import org.dspace.core.AbstractHibernateDAO; +import org.dspace.core.Context; + +public class RelationshipDAOImpl extends AbstractHibernateDAO implements RelationshipDAO { + + @Override + public List findByItem(Context context, Item item) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Relationship.class); + Root relationshipRoot = criteriaQuery.from(Relationship.class); + criteriaQuery.select(relationshipRoot); + criteriaQuery + .where(criteriaBuilder.or(criteriaBuilder.equal(relationshipRoot.get(Relationship_.leftItem), item), + criteriaBuilder.equal(relationshipRoot.get(Relationship_.rightItem), item))); + return list(context, criteriaQuery, false, Relationship.class, -1, -1); + } + + @Override + public int findLeftPlaceByLeftItem(Context context, Item item) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Relationship.class); + Root relationshipRoot = criteriaQuery.from(Relationship.class); + criteriaQuery.select(relationshipRoot); + criteriaQuery.where(criteriaBuilder.equal(relationshipRoot.get(Relationship_.leftItem), item)); + List list = list(context, criteriaQuery, false, Relationship.class, -1, -1); + list.sort((o1, o2) -> o2.getLeftPlace() - o1.getLeftPlace()); + if (!list.isEmpty()) { + return list.get(0).getLeftPlace(); + } else { + return 1; + } + } + + @Override + public int findRightPlaceByRightItem(Context context, Item item) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Relationship.class); + Root relationshipRoot = criteriaQuery.from(Relationship.class); + criteriaQuery.select(relationshipRoot); + criteriaQuery.where(criteriaBuilder.equal(relationshipRoot.get(Relationship_.rightItem), item)); + List list = list(context, criteriaQuery, false, Relationship.class, -1, -1); + list.sort((o1, o2) -> o2.getLeftPlace() - o1.getLeftPlace()); + if (!list.isEmpty()) { + return list.get(0).getLeftPlace(); + } else { + return 1; + } + } + + @Override + public List findByRelationshipType(Context context, RelationshipType relationshipType) + throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Relationship.class); + Root relationshipRoot = criteriaQuery.from(Relationship.class); + criteriaQuery.select(relationshipRoot); + criteriaQuery + .where(criteriaBuilder.equal(relationshipRoot.get(Relationship_.relationshipType), relationshipType)); + return list(context, criteriaQuery, true, Relationship.class, -1, -1); + } + + +} diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipTypeDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipTypeDAOImpl.java new file mode 100644 index 0000000000..b4eaece569 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/RelationshipTypeDAOImpl.java @@ -0,0 +1,73 @@ +/** + * 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.dao.impl; + +import java.sql.SQLException; +import java.util.List; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.dspace.content.EntityType; +import org.dspace.content.RelationshipType; +import org.dspace.content.RelationshipType_; +import org.dspace.content.dao.RelationshipTypeDAO; +import org.dspace.core.AbstractHibernateDAO; +import org.dspace.core.Context; + +public class RelationshipTypeDAOImpl extends AbstractHibernateDAO implements RelationshipTypeDAO { + + @Override + public RelationshipType findByTypesAndLabels(Context context, EntityType leftType, EntityType rightType, + String leftLabel, String rightLabel) + throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, RelationshipType.class); + Root relationshipTypeRoot = criteriaQuery.from(RelationshipType.class); + criteriaQuery.select(relationshipTypeRoot); + criteriaQuery.where( + criteriaBuilder.and(criteriaBuilder.equal(relationshipTypeRoot.get(RelationshipType_.leftType), leftType), + criteriaBuilder.equal(relationshipTypeRoot.get(RelationshipType_.rightType), rightType), + criteriaBuilder.equal(relationshipTypeRoot.get(RelationshipType_.leftLabel), leftLabel), + criteriaBuilder + .equal(relationshipTypeRoot.get(RelationshipType_.rightLabel), rightLabel))); + return uniqueResult(context, criteriaQuery, false, RelationshipType.class, -1, -1); + } + + @Override + public List findByLeftOrRightLabel(Context context, String label) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, RelationshipType.class); + Root relationshipTypeRoot = criteriaQuery.from(RelationshipType.class); + criteriaQuery.select(relationshipTypeRoot); + criteriaQuery.where( + criteriaBuilder.or( + criteriaBuilder.equal(relationshipTypeRoot.get(RelationshipType_.leftLabel), label), + criteriaBuilder.equal(relationshipTypeRoot.get(RelationshipType_.rightLabel), label) + ) + ); + return list(context, criteriaQuery, true, RelationshipType.class, -1, -1); + } + + @Override + public List findByEntityType(Context context, EntityType entityType) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, RelationshipType.class); + Root relationshipTypeRoot = criteriaQuery.from(RelationshipType.class); + criteriaQuery.select(relationshipTypeRoot); + criteriaQuery.where( + criteriaBuilder.or(criteriaBuilder. + equal(relationshipTypeRoot.get(RelationshipType_.leftType), entityType), + criteriaBuilder + .equal(relationshipTypeRoot.get(RelationshipType_.rightType), entityType) + ) + ); + return list(context, criteriaQuery, false, RelationshipType.class, -1, -1); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java index 3098929aeb..ab09eb2347 100644 --- a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java +++ b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactory.java @@ -20,6 +20,8 @@ import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; import org.dspace.content.service.DSpaceObjectLegacySupportService; import org.dspace.content.service.DSpaceObjectService; +import org.dspace.content.service.EntityService; +import org.dspace.content.service.EntityTypeService; import org.dspace.content.service.InProgressSubmissionService; import org.dspace.content.service.IndexableObjectService; import org.dspace.content.service.InstallItemService; @@ -27,6 +29,8 @@ import org.dspace.content.service.ItemService; import org.dspace.content.service.MetadataFieldService; import org.dspace.content.service.MetadataSchemaService; import org.dspace.content.service.MetadataValueService; +import org.dspace.content.service.RelationshipService; +import org.dspace.content.service.RelationshipTypeService; import org.dspace.content.service.SiteService; import org.dspace.content.service.SupervisedItemService; import org.dspace.content.service.WorkspaceItemService; @@ -80,6 +84,34 @@ public abstract class ContentServiceFactory { public abstract SiteService getSiteService(); + /** + * Return the implementation of the RelationshipTypeService interface + * + * @return the RelationshipTypeService + */ + public abstract RelationshipTypeService getRelationshipTypeService(); + + /** + * Return the implementation of the RelationshipService interface + * + * @return the RelationshipService + */ + public abstract RelationshipService getRelationshipService(); + + /** + * Return the implementation of the EntityTypeService interface + * + * @return the EntityTypeService + */ + public abstract EntityTypeService getEntityTypeService(); + + /** + * Return the implementation of the EntityService interface + * + * @return the EntityService + */ + public abstract EntityService getEntityService(); + public InProgressSubmissionService getInProgressSubmissionService(InProgressSubmission inProgressSubmission) { if (inProgressSubmission instanceof WorkspaceItem) { return getWorkspaceItemService(); diff --git a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java index 589a2c4268..e55b7bfe42 100644 --- a/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/factory/ContentServiceFactoryImpl.java @@ -17,12 +17,16 @@ import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; import org.dspace.content.service.DSpaceObjectLegacySupportService; import org.dspace.content.service.DSpaceObjectService; +import org.dspace.content.service.EntityService; +import org.dspace.content.service.EntityTypeService; import org.dspace.content.service.IndexableObjectService; import org.dspace.content.service.InstallItemService; import org.dspace.content.service.ItemService; import org.dspace.content.service.MetadataFieldService; import org.dspace.content.service.MetadataSchemaService; import org.dspace.content.service.MetadataValueService; +import org.dspace.content.service.RelationshipService; +import org.dspace.content.service.RelationshipTypeService; import org.dspace.content.service.SiteService; import org.dspace.content.service.SupervisedItemService; import org.dspace.content.service.WorkspaceItemService; @@ -69,6 +73,15 @@ public class ContentServiceFactoryImpl extends ContentServiceFactory { @Autowired(required = true) private SiteService siteService; + @Autowired(required = true) + private RelationshipService relationshipService; + @Autowired(required = true) + private RelationshipTypeService relationshipTypeService; + @Autowired(required = true) + private EntityTypeService entityTypeService; + @Autowired(required = true) + private EntityService entityService; + @Override public List getIndexableObjectServices() { return new DSpace().getServiceManager().getServicesByType(IndexableObjectService.class); @@ -149,4 +162,24 @@ public class ContentServiceFactoryImpl extends ContentServiceFactory { return siteService; } + @Override + public RelationshipTypeService getRelationshipTypeService() { + return relationshipTypeService; + } + + @Override + public RelationshipService getRelationshipService() { + return relationshipService; + } + + @Override + public EntityTypeService getEntityTypeService() { + return entityTypeService; + } + + @Override + public EntityService getEntityService() { + return entityService; + } + } diff --git a/dspace-api/src/main/java/org/dspace/content/packager/PDFPackager.java b/dspace-api/src/main/java/org/dspace/content/packager/PDFPackager.java index d80efb06e4..c6036d2261 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/PDFPackager.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/PDFPackager.java @@ -33,7 +33,7 @@ import org.dspace.content.Collection; import org.dspace.content.DCDate; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.WorkspaceItem; import org.dspace.content.crosswalk.CrosswalkException; import org.dspace.content.crosswalk.MetadataValidationException; @@ -377,10 +377,11 @@ public class PDFPackager if (log.isDebugEnabled()) { log.debug("PDF Info dict title=\"" + title + "\""); } - itemService.addMetadata(context, item, MetadataSchema.DC_SCHEMA, "title", null, "en", title); + itemService.addMetadata(context, item, MetadataSchemaEnum.DC.getName(), "title", null, "en", title); String value = docinfo.getAuthor(); if (value != null) { - itemService.addMetadata(context, item, MetadataSchema.DC_SCHEMA, "contributor", "author", null, value); + itemService.addMetadata(context, item, MetadataSchemaEnum.DC.getName(), + "contributor", "author", null, value); if (log.isDebugEnabled()) { log.debug("PDF Info dict author=\"" + value + "\""); } @@ -388,25 +389,29 @@ public class PDFPackager value = docinfo.getCreator(); if (value != null) { - itemService.addMetadata(context, item, MetadataSchema.DC_SCHEMA, "description", "provenance", "en", + itemService.addMetadata(context, item, MetadataSchemaEnum.DC.getName(), + "description", "provenance", "en", "Application that created the original document: " + value); } value = docinfo.getProducer(); if (value != null) { - itemService.addMetadata(context, item, MetadataSchema.DC_SCHEMA, "description", "provenance", "en", + itemService.addMetadata(context, item, MetadataSchemaEnum.DC.getName(), + "description", "provenance", "en", "Original document converted to PDF by: " + value); } value = docinfo.getSubject(); if (value != null) { itemService - .addMetadata(context, item, MetadataSchema.DC_SCHEMA, "description", "abstract", null, value); + .addMetadata(context, item, MetadataSchemaEnum.DC.getName(), + "description", "abstract", null, value); } value = docinfo.getKeywords(); if (value != null) { - itemService.addMetadata(context, item, MetadataSchema.DC_SCHEMA, "subject", "other", null, value); + itemService.addMetadata(context, item, MetadataSchemaEnum.DC.getName(), + "subject", "other", null, value); } // Take either CreationDate or ModDate as "date.created", @@ -417,7 +422,7 @@ public class PDFPackager } if (calValue != null) { - itemService.addMetadata(context, item, MetadataSchema.DC_SCHEMA, "date", "created", null, + itemService.addMetadata(context, item, MetadataSchemaEnum.DC.getName(), "date", "created", null, (new DCDate(calValue.getTime())).toString()); } itemService.update(context, item); diff --git a/dspace-api/src/main/java/org/dspace/content/packager/PackageUtils.java b/dspace-api/src/main/java/org/dspace/content/packager/PackageUtils.java index 2f4074063c..1fde79d71d 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/PackageUtils.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/PackageUtils.java @@ -30,7 +30,7 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.MetadataValue; import org.dspace.content.WorkspaceItem; import org.dspace.content.factory.ContentServiceFactory; @@ -158,7 +158,7 @@ public class PackageUtils { */ public static void checkItemMetadata(Item item) throws PackageValidationException { - List t = itemService.getMetadata(item, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY); + List t = itemService.getMetadata(item, MetadataSchemaEnum.DC.getName(), "title", null, Item.ANY); if (t == null || t.size() == 0) { throw new PackageValidationException("Item cannot be created without the required \"title\" DC metadata."); } diff --git a/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java b/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java index 270b2799d9..753516a373 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/DSpaceObjectService.java @@ -50,7 +50,6 @@ public interface DSpaceObjectService { */ public abstract String getName(T dso); - /** * Tries to lookup all Identifiers of this DSpaceObject. * diff --git a/dspace-api/src/main/java/org/dspace/content/service/EntityService.java b/dspace-api/src/main/java/org/dspace/content/service/EntityService.java new file mode 100644 index 0000000000..ec603d7690 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/service/EntityService.java @@ -0,0 +1,127 @@ +/** + * 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.service; + +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; + +import org.dspace.content.Entity; +import org.dspace.content.EntityType; +import org.dspace.content.Relationship; +import org.dspace.content.RelationshipType; +import org.dspace.core.Context; + +/** + * This Service provides us with a few methods to return objects based on the Entity object. + * Since the Entity object isn't a database object, this method mostly outsources to getters for other services + * to return the wanted objects to then check for properties on either the list of relationships or the item included + * in the Entity. + */ +public interface EntityService { + + /** + * This will construct an Entity object that will be returned with the Item that matches the ItemID that was + * passed along + * as well as a list of relationships for that Item. + * @param context The relevant DSpace context + * @param itemId The ItemID for the Item that is to be used in the Entity object + * @return The constructed Entity object with the Item and the list of relationships + * @throws SQLException If something goes wrong + */ + Entity findByItemId(Context context, UUID itemId) throws SQLException; + + /** + * Returns the EntityType for the Item that is attached to the Entity that is passed along to this method. + * The EntityType String logic is in the Metadata for that Item and will be searched on in the EntityTypeService + * to retrieve the actual EntityType object + * @param context The relevant DSpace context + * @param entity The Entity object which contains the Item + * @return The EntityType that belongs to this Item + * @throws SQLException If something goes wrong + */ + EntityType getType(Context context, Entity entity) throws SQLException; + + /** + * Retrieves the list of relationships, which are attached to the Entity object that is passed along, where the + * left item object of each relationship is equal to the Item object of the Entity object that is passed along + * @param context The relevant DSpace context + * @param entity The Entity object to be returned + * @return The list of relationships that have the Item in the Entity object as their left item + */ + List getLeftRelations(Context context, Entity entity); + + /** + * Retrieves the list of relationships, which are attached to the Entity object that is passed along, where the + * right item object of each relationship is equal to the Item object of the Entity object that is passed along + * @param context The relevant DSpace context + * @param entity The Entity object to be returned + * @return The list of relationships that have the Item in the Entity object as their right item + */ + List getRightRelations(Context context, Entity entity); + + /** + * Retrieves the list of relationships for which their relationshiptype has a left or right label that is + * equal to the passed along label String + * @param context The relevant DSpace context + * @param label The label that needs to be in the relationshiptype of the relationship + * @return The list of relationships that have a relationshiptype with a left or right label + * that is equal to the label param + * @throws SQLException If something goes wrong + */ + List getRelationsByLabel(Context context, String label) throws SQLException; + + /** + * Retrieves the list of relationships that have a relationshiptype that contains the EntityType for the given + * Entity + * in either the leftEntityType or the rightEntityType variables + * @param context The relevant DSpace context + * @param entity The Entity for which the EntityType should be checked for relationships + * @return The list of relationships that each contain a relationshiptype in which there is a right or left + * entity type that + * is equal to the entity type for the given entity + * @throws SQLException If something goes wrong + */ + List getAllRelationshipTypes(Context context, Entity entity) throws SQLException; + + /** + * Retrieves the list of relationships that have a relationshiptype that contains the EntityType for the given + * Entity + * in the leftEntityType + * @param context The relevant DSpace context + * @param entity The Entity for which the EntityType should be checked for relationships + * @return The list of relationships that each contain a relationshiptype in which there is a left entity type that + * is equal to the entity type for the given entity + * @throws SQLException If something goes wrong + */ + List getLeftRelationshipTypes(Context context, Entity entity) throws SQLException; + + /** + * Retrieves the list of relationships that have a relationshiptype that contains the EntityType for the given + * Entity + * in the rightEntityType + * @param context The relevant DSpace context + * @param entity The Entity for which the EntityType should be checked for relationships + * @return The list of relationships that each contain a relationshiptype in which there is a right entity type that + * is equal to the entity type for the given entity + * @throws SQLException If something goes wrong + */ + List getRightRelationshipTypes(Context context, Entity entity) throws SQLException; + + /** + * Retrieves a list of RelationshipType objects for which either their left or right label is equal to the + * label parameter that's being passed along + * @param context The relevant DSpace context + * @param label The label for which the relationshiptype's labels must be checked + * @return The list of relationshiptypes that each contain a left or right label that is equal + * to the given label parameter + * @throws SQLException If something goes wrong + */ + List getRelationshipTypesByLabel(Context context, String label) throws SQLException; + +} diff --git a/dspace-api/src/main/java/org/dspace/content/service/EntityTypeService.java b/dspace-api/src/main/java/org/dspace/content/service/EntityTypeService.java new file mode 100644 index 0000000000..32c86848f8 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/service/EntityTypeService.java @@ -0,0 +1,49 @@ +/** + * 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.service; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.EntityType; +import org.dspace.core.Context; +import org.dspace.service.DSpaceCRUDService; + +/** + * This Service is used to access the data for EntityTypes through the DAO objects + */ +public interface EntityTypeService extends DSpaceCRUDService { + + /** + * Retrieves the EntityType that has the entityType String parameter as label + * @param context The relevant DSpace context + * @param entityType The String label that has to match + * @return The EntityType that has a String + * @throws SQLException If something goes wrong + */ + public EntityType findByEntityType(Context context,String entityType) throws SQLException; + + /** + * Retrieves all the EntityType objects currently in the system + * @param context The relevant DSpace context + * @return A list of all EntityType objects + * @throws SQLException If something goes wrong + */ + public List findAll(Context context) throws SQLException; + + /** + * This method creates an EntityType object in the database with the given entityTypeString as it's label + * @param context The relevant DSpace context + * @param entityTypeString The label for the newly created EntityType + * @return The newly created EntityType + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something geos wrong with authorizations + */ + public EntityType create(Context context, String entityTypeString) throws SQLException, AuthorizeException; +} diff --git a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java index 4b811a1bee..05992764c1 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/ItemService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/ItemService.java @@ -23,8 +23,11 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.content.MetadataField; +import org.dspace.content.MetadataValue; +import org.dspace.content.RelationshipMetadataValue; import org.dspace.content.Thumbnail; import org.dspace.content.WorkspaceItem; +import org.dspace.content.virtual.VirtualMetadataPopulator; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; @@ -651,4 +654,69 @@ public interface ItemService */ boolean isInProgressSubmission(Context context, Item item) throws SQLException; + /** + * This method retrieves a list of MetadataValue objects that get constructed from processing + * the given Item's Relationships through the config given to the {@link VirtualMetadataPopulator} + * @param item The Item that will be processed through it's Relationships + * @param enableVirtualMetadata This parameter will determine whether the list of Relationship metadata + * should be populated with metadata that is being generated through the + * VirtualMetadataPopulator functionality or not + * @return The list of MetadataValue objects constructed through the Relationships + */ + public List getRelationshipMetadata(Item item, boolean enableVirtualMetadata); + + + + /** + * Get metadata for the DSpace Object in a chosen schema. + * See MetadataSchema for more information about schemas. + * Passing in a null value for qualifier + * or lang only matches metadata fields where that + * qualifier or languages is actually null. + * Passing in DSpaceObject.ANY + * retrieves all metadata fields with any value for the qualifier or + * language, including null + *

+ * Examples: + *

+ * Return values of the unqualified "title" field, in any language. + * Qualified title fields (e.g. "title.uniform") are NOT returned: + *

+ * dspaceobject.getMetadataByMetadataString("dc", "title", null, DSpaceObject.ANY ); + *

+ * Return all US English values of the "title" element, with any qualifier + * (including unqualified): + *

+ * dspaceobject.getMetadataByMetadataString("dc, "title", DSpaceObject.ANY, "en_US" ); + *

+ * The ordering of values of a particular element/qualifier/language + * combination is significant. When retrieving with wildcards, values of a + * particular element/qualifier/language combinations will be adjacent, but + * the overall ordering of the combinations is indeterminate. + * + * If enableVirtualMetadata is set to false, the virtual metadata will not be included + * + * @param item Item + * @param schema the schema for the metadata field. Must match + * the name of an existing metadata schema. + * @param element the element name. DSpaceObject.ANY matches any + * element. null doesn't really make sense as all + * metadata must have an element. + * @param qualifier the qualifier. null means unqualified, and + * DSpaceObject.ANY means any qualifier (including + * unqualified.) + * @param lang the ISO639 language code, optionally followed by an underscore + * and the ISO3166 country code. null means only + * values with no language are returned, and + * DSpaceObject.ANY means values with any country code or + * no country code are returned. + * @param enableVirtualMetadata + * Enables virtual metadata calculation and inclusion from the + * relationships. + * @return metadata fields that match the parameters + */ + public List getMetadata(Item item, String schema, String element, String qualifier, + String lang, boolean enableVirtualMetadata); + + } diff --git a/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java b/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java new file mode 100644 index 0000000000..6b07c316a1 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java @@ -0,0 +1,144 @@ +/** + * 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.service; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.Relationship; +import org.dspace.content.RelationshipType; +import org.dspace.core.Context; +import org.dspace.service.DSpaceCRUDService; + +/** + * This Service will use the DAO classes to access the information about Relationships from the database + */ +public interface RelationshipService extends DSpaceCRUDService { + + /** + * Retrieves the list of Relationships currently in the system for which the given Item is either + * a leftItem or a rightItem object + * @param context The relevant DSpace context + * @param item The Item that has to be the left or right item for the relationship to be included in the list + * @return The list of relationships for which each relationship adheres to the above listed constraint + * @throws SQLException If something goes wrong + */ + public List findByItem(Context context,Item item) throws SQLException; + + /** + * Retrieves the full list of relationships currently in the system + * @param context The relevant DSpace context + * @return The list of all relationships currently in the system + * @throws SQLException If something goes wrong + */ + public List findAll(Context context) throws SQLException; + + /** + * This method creates a relationship object in the database equal to the given relationship param + * if this is a valid relationship + * @param context The relevant DSpace context + * @param relationship The relationship that will be created in the database if it is valid + * @return The created relationship with updated place variables + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong with authorizations + */ + public Relationship create(Context context, Relationship relationship) throws SQLException, AuthorizeException; + + /** + * Retrieves the highest integer value for the leftplace property of a Relationship for all relationships + * that have the given item as a left item + * @param context The relevant DSpace context + * @param item The item that has to be the leftItem of a relationship for it to qualify + * @return The integer value of the highest left place property of all relationships + * that have the given item as a leftitem property + * @throws SQLException If something goes wrong + */ + int findLeftPlaceByLeftItem(Context context, Item item) throws SQLException; + + /** + * Retrieves the highest integer value for the rightplace property of a Relationship for all relationships + * that have the given item as a right item + * @param context The relevant DSpace context + * @param item The item that has to be the rightitem of a relationship for it to qualify + * @return The integer value of the highest right place property of all relationships + * that have the given item as a rightitem property + * @throws SQLException If something goes wrong + */ + int findRightPlaceByRightItem(Context context, Item item) throws SQLException; + + /** + * This method returns a list of Relationships for which the leftItem or rightItem is equal to the given + * Item object and for which the RelationshipType object is equal to the relationshipType property + * @param context The relevant DSpace context + * @param item The Item object to be matched on the leftItem or rightItem for the relationship + * @param relationshipType The RelationshipType object that will be used to check the Relationship on + * @return The list of Relationship objects that have the given Item object as leftItem or rightItem and + * for which the relationshipType property is equal to the given RelationshipType + * @throws SQLException If something goes wrong + */ + public List findByItemAndRelationshipType(Context context, Item item, + RelationshipType relationshipType) + throws SQLException; + + /** + * This method will update the place for the Relationship and all other relationships found by the items and + * relationship type of the given Relatonship. It will give this Relationship the last place in both the + * left and right place determined by querying for the list of leftRelationships and rightRelationships + * by the leftItem, rightItem and relationshipType of the given Relationship. + * @param context The relevant DSpace context + * @param relationship The Relationship object that will have it's place updated and that will be used + * to retrieve the other relationships whose place might need to be updated + * @param isCreation Is the relationship new or did it already exist + * @throws SQLException If something goes wrong + */ + public void updatePlaceInRelationship(Context context, Relationship relationship, boolean isCreation) + throws SQLException, AuthorizeException; + + /** + * This method will update the given item's metadata order. + * If the relationships for the item have been modified and will calculate the place based on a + * metadata field, this function will ensure the place is calculated. + * @param context The relevant DSpace context + * @param relatedItem The Item for which the list of Relationship location is calculated + * based on a metadata field + * @throws SQLException If something goes wrong + * @throws AuthorizeException + * If the user is not authorized to update the item + */ + public void updateItem(Context context, Item relatedItem) throws SQLException, AuthorizeException; + + + /** + * This method returns a list of Relationship objets for which the relationshipType property is equal to the given + * RelationshipType object + * @param context The relevant DSpace context + * @param relationshipType The RelationshipType object that will be used to check the Relationship on + * @return The list of Relationship objects for which the given RelationshipType object is equal + * to the relationshipType property + * @throws SQLException If something goes wrong + */ + List findByRelationshipType(Context context, RelationshipType relationshipType) throws SQLException; + + /** + * This method is used to construct a Relationship object with all it's variables + * @param c The relevant DSpace context + * @param leftItem The leftItem Item object for the relationship + * @param rightItem The rightItem Item object for the relationship + * @param relationshipType The RelationshipType object for the relationship + * @param leftPlace The leftPlace integer for the relationship + * @param rightPlace The rightPlace integer for the relationship + * @return The created Relationship object with the given properties + * @throws AuthorizeException If something goes wrong + * @throws SQLException If something goes wrong + */ + Relationship create(Context c, Item leftItem, Item rightItem, RelationshipType relationshipType, + int leftPlace, int rightPlace) + throws AuthorizeException, SQLException; +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/content/service/RelationshipTypeService.java b/dspace-api/src/main/java/org/dspace/content/service/RelationshipTypeService.java new file mode 100644 index 0000000000..0a5a51d4aa --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/service/RelationshipTypeService.java @@ -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.content.service; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.EntityType; +import org.dspace.content.RelationshipType; +import org.dspace.core.Context; +import org.dspace.service.DSpaceCRUDService; + +/** + * This Service uses DAOs to access information on the database objects for the RelationshipTypes + */ +public interface RelationshipTypeService extends DSpaceCRUDService { + + /** + * This method creates the given RelationshipType object in the database and returns it + * @param context The relevant DSpace context + * @param relationshipType The RelationshipType to be created in the database + * @return The newly created RelationshipType + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong with authorizations + */ + RelationshipType create(Context context,RelationshipType relationshipType) throws SQLException, AuthorizeException; + + /** + * Retrieves a RelationshipType for which the given parameters all match the one in the returned RelationshipType + * @param context The relevant DSpace context + * @param leftType The rightType EntityType that needs to match for the returned RelationshipType + * @param rightType The rightType EntityType that needs to match for the returned RelationshipType + * @param leftLabel The leftLabel String that needs to match for the returned RelationshipType + * @param rightLabel The rightLabel String that needs to match for the returned RelationshipType + * @return + * @throws SQLException If something goes wrong + */ + RelationshipType findbyTypesAndLabels(Context context,EntityType leftType,EntityType rightType, + String leftLabel,String rightLabel) + throws SQLException; + + /** + * Retrieves all RelationshipType objects currently in the system + * @param context The relevant DSpace context + * @return The list of all RelationshipType objects currently in the system + * @throws SQLException If something goes wrong + */ + List findAll(Context context) throws SQLException; + + /** + * Retrieves all RelationshipType objects that have a left or right label that is + * equal to the given String + * @param context The relevant DSpace context + * @param label The label that has to match + * @return The list of all RelationshipType objects that have a left or right label + * that is equal to the given label param + * @throws SQLException If something goes wrong + */ + List findByLeftOrRightLabel(Context context, String label) throws SQLException; + + /** + * Returns a list of RelationshipType objects for which the given EntityType is equal to either the leftType + * or the rightType + * @param context The relevant DSpace context + * @param entityType The EntityType object used to check the leftType and rightType properties + * @return A list of RelationshipType objects for which the leftType or rightType property are equal to the + * given EntityType object + * @throws SQLException If something goes wrong + */ + List findByEntityType(Context context, EntityType entityType) throws SQLException; + + /** + * This method will support the creation of a RelationshipType object with the given parameters + * @param context The relevant DSpace context + * @param leftEntityType The leftEntityType EntityType object for this relationshipType + * @param rightEntityType The rightEntityType EntityType object for this relationshipType + * @param leftLabel The leftLabel String object for this relationshipType + * @param rightLabel The rightLabel String object for this relationshipType + * @param leftCardinalityMinInteger The leftCardinalityMinInteger Integer object for this relationshipType + * @param leftCardinalityMaxInteger The leftCardinalityMaxInteger Integer object for this relationshipType + * @param rightCardinalityMinInteger The rightCardinalityMinInteger Integer object for this relationshipType + * @param rightCardinalityMaxInteger The rightCardinalityMaxInteger Integer object for this relationshipType + * @return The created RelationshipType object for these properties + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong + */ + RelationshipType create(Context context, EntityType leftEntityType, EntityType rightEntityType, String leftLabel, + String rightLabel, Integer leftCardinalityMinInteger, Integer leftCardinalityMaxInteger, + Integer rightCardinalityMinInteger, Integer rightCardinalityMaxInteger) + throws SQLException, AuthorizeException; +} diff --git a/dspace-api/src/main/java/org/dspace/content/virtual/Collected.java b/dspace-api/src/main/java/org/dspace/content/virtual/Collected.java new file mode 100644 index 0000000000..4820e34e4e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/virtual/Collected.java @@ -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.content.virtual; + +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * A bean implementing the {@link VirtualMetadataConfiguration} interface to achieve the generation of Virtual + * metadata + * The Collected bean will take all the values of each metadata field defined in the list and it'll + * create a list of virtual metadata fields defined by the map in which it's defined. + * All values from the metadata fields will returned as separate elements + */ +public class Collected implements VirtualMetadataConfiguration { + + @Autowired + private ItemService itemService; + + /** + * This property determines whether this RelationshipMetadataValue should be used in place calculation or not. + * This is retrieved from Spring configuration when constructing RelationshipMetadataValues. This Spring + * configuration is located in the core-services.xml configuration file. + * Putting this property on true will imply that we're now mixing plain-text metadatavalues with the + * metadatavalues that are constructed through Relationships with regards to the place attribute. + * For example, currently the RelationshipMetadataValue dc.contributor.author that is constructed through a + * Relationship for a Publication will have its useForPlace set to true. This means that the place + * calculation will take both these RelationshipMetadataValues into account together with the normal + * plain text metadatavalues. + */ + private boolean useForPlace; + /** + * The fields for which the metadata will be retrieved + */ + private List fields; + + /** + * Generic getter for the fields property + * @return The list of fields to be used in this bean + */ + public List getFields() { + return fields; + } + + /** + * Generic setter for the fields property + * @param fields the list of fields to which the fields property will be set to + */ + public void setFields(List fields) { + this.fields = fields; + } + /** + * Generic setter for the useForPlace property + * @param useForPlace The boolean value that the useForPlace property will be set to + */ + public void setUseForPlace(boolean useForPlace) { + this.useForPlace = useForPlace; + } + + /** + * Generic getter for the useForPlace property + * @return The useForPlace to be used by this bean + */ + public boolean getUseForPlace() { + return useForPlace; + } + + /** + * this method will retrieve the metadata values from the given item for all the metadata fields listed + * in the fields property and it'll return all those values as a list + * @param context The relevant DSpace context + * @param item The item that will be used to either retrieve metadata values from + * @return The String values for all of the retrieved metadatavalues + */ + public List getValues(Context context, Item item) { + List resultValues = new LinkedList<>(); + List value = this.getFields(); + for (String s : value) { + String[] splittedString = s.split("\\."); + + List resultList = itemService.getMetadata(item, + splittedString.length > 0 ? splittedString[0] : + null, + splittedString.length > 1 ? splittedString[1] : + null, + splittedString.length > 2 ? splittedString[2] : + null, + Item.ANY, false); + + for (MetadataValue metadataValue : resultList) { + if (StringUtils.isNotBlank(metadataValue.getValue())) { + resultValues.add(metadataValue.getValue()); + } + } + } + + return resultValues; + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/virtual/Concatenate.java b/dspace-api/src/main/java/org/dspace/content/virtual/Concatenate.java new file mode 100644 index 0000000000..04d3e911ec --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/virtual/Concatenate.java @@ -0,0 +1,140 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content.virtual; + +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * A bean implementing the {@link VirtualMetadataConfiguration} interface to achieve the generation of Virtual + * metadata + * The Concatenate bean will take all the values of each metadata field configured in the list + * and it will join all of these together with the separator defined in this bean. This means that whichever + * entry this bean belongs to, that metadata field will have the value of the related item's metadata values + * joined together with this separator. Only one value will be returned + */ +public class Concatenate implements VirtualMetadataConfiguration { + + @Autowired + private ItemService itemService; + + /** + * The fields for which the metadata will be retrieved + */ + private List fields; + /** + * The separator that will be used to concatenate the values retrieved from the above mentioned fields + */ + private String separator; + + /** + * The boolean value indicating whether this field should be used for place or not + */ + private boolean useForPlace = false; + + /** + * Generic getter for the fields property + * @return The list of fields to be used in this bean + */ + public List getFields() { + return fields; + } + + /** + * Generic setter for the fields property + * @param fields the list of fields to which the fields property will be set to + */ + public void setFields(List fields) { + this.fields = fields; + } + + /** + * Generic getter for the seperator + * @return the seperator to be used by this bean + */ + public String getSeparator() { + return separator; + } + + /** + * Generic setter for the seperator property + * @param separator The String seperator value to which this seperator value will be set to + */ + public void setSeparator(String separator) { + this.separator = separator; + } + + /** + * Generic setter for the useForPlace property + * @param useForPlace The boolean value that the useForPlace property will be set to + */ + public void setUseForPlace(boolean useForPlace) { + this.useForPlace = useForPlace; + } + + /** + * Generic getter for the useForPlace property + * @return The useForPlace to be used by this bean + */ + public boolean getUseForPlace() { + return useForPlace; + } + + /** + * this method will retrieve the metadata values from the given item for all the metadata fields listed + * in the fields property and it'll concatenate all those values together with the separator specified + * in this class + * @param context The relevant DSpace context + * @param item The item that will be used to either retrieve metadata values from + * @return The String value for all of the retrieved metadatavalues combined with the separator + */ + public List getValues(Context context, Item item) { + + List resultValues = new LinkedList<>(); + List value = this.getFields(); + for (String s : value) { + String[] splittedString = s.split("\\."); + + List resultList = itemService.getMetadata(item, + splittedString.length > 0 ? splittedString[0] : + null, + splittedString.length > 1 ? splittedString[1] : + null, + splittedString.length > 2 ? splittedString[2] : + null, + Item.ANY, false); + + String resultString = ""; + for (int i = 0; i < resultList.size(); i++) { + String metadataValueString = resultList.get(i).getValue(); + if (StringUtils.isNotBlank(metadataValueString)) { + if (StringUtils.isNotBlank(resultString)) { + resultString += this.getSeparator(); + } + resultString += metadataValueString; + } + } + if (StringUtils.isNotBlank(resultString)) { + resultValues.add(resultString); + } + } + + String result = StringUtils.join(resultValues, this.getSeparator()); + List listToReturn = new LinkedList<>(); + listToReturn.add(result); + return listToReturn; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/content/virtual/EntityTypeToFilterQueryService.java b/dspace-api/src/main/java/org/dspace/content/virtual/EntityTypeToFilterQueryService.java new file mode 100644 index 0000000000..dff51bda08 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/virtual/EntityTypeToFilterQueryService.java @@ -0,0 +1,58 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content.virtual; + +import java.util.Map; + +/** + * This service offers a way to convert EntityType String labels to a filter query which is defined in the + * bean config for this service + */ +public class EntityTypeToFilterQueryService { + + /** + * This map contains the mapping between the String label and the String for the filter query + * e.g. + */ + private Map map; + + + /** + * Standard setter for this map + * @param map The map that should be set in this service + */ + public void setMap(Map map) { + this.map = map; + } + + /** + * Standard getter for the map + * @return the map + */ + public Map getMap() { + return map; + } + + /** + * Retrieves the filterQuery for the key that's given as a parameter. It looks in the map for the value + * @param key The key for which we'll find the value in the map + * @return The filter query representation for the given key + */ + public String getFilterQueryForKey(String key) { + return map.get(key); + } + + /** + * Returns a boolean depending on whether a key is present in the map or not + * @param key The key to be checked for + * @return The boolean indicating whether this key is present in the map or not + */ + public boolean hasKey(String key) { + return map.containsKey(key); + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/virtual/Related.java b/dspace-api/src/main/java/org/dspace/content/virtual/Related.java new file mode 100644 index 0000000000..09eb951f06 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/virtual/Related.java @@ -0,0 +1,181 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content.virtual; + +import java.sql.SQLException; +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.content.Entity; +import org.dspace.content.EntityType; +import org.dspace.content.Item; +import org.dspace.content.Relationship; +import org.dspace.content.RelationshipType; +import org.dspace.content.service.EntityService; +import org.dspace.content.service.EntityTypeService; +import org.dspace.content.service.RelationshipService; +import org.dspace.content.service.RelationshipTypeService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * A bean implementing the {@link VirtualMetadataConfiguration} interface to achieve the generation of + * Virtual metadata by traversing the path of relation specified in the config for this bean + * The Related bean will find the relationshiptype defined in the relationshipTypeString property on + * the current item and it'll use the related item from that relationship to pass it along to the + * virtualMetadataConfiguration property which in turn refers to another VirtualBean instance and it continues + * the chain until it reaches either a Concatenate or Collected bean to retrieve the values. It will then return + * that value through the chain again and it'll fill the values into the virtual metadata fields that are defined + * in the map for the first Related bean. + */ +public class Related implements VirtualMetadataConfiguration { + + @Autowired + private RelationshipTypeService relationshipTypeService; + + @Autowired + private RelationshipService relationshipService; + + @Autowired + private EntityTypeService entityTypeService; + + @Autowired + private EntityService entityService; + + /** + * The String representing the relationshipType that needs to be used to find the next item + */ + private String relationshipTypeString; + /** + * The left or right place that this relationship needs to have to retrieve the proper item + */ + private Integer place; + /** + * The next bean to call its getValues() method on + */ + private VirtualMetadataConfiguration virtualMetadataConfiguration; + + /** + * The boolean value indicating whether this field should be used for place or not + */ + private boolean useForPlace = false; + + /** + * Generic getter for the relationshipTypeString property of this class + * @return The relationshipTypeString property + */ + public String getRelationshipTypeString() { + return relationshipTypeString; + } + + /** + * Generic setter for the relationshipTypeString property of this class + * @param relationshipTypeString The String to which the relationshipTypeString will be set to + */ + public void setRelationshipTypeString(String relationshipTypeString) { + this.relationshipTypeString = relationshipTypeString; + } + + /** + * Generic getter for the place property of this class + * @return The place property + */ + public Integer getPlace() { + return place; + } + + /** + * Generic setter for the place property of this class + * @param place The Integer to which the place property will be set to + */ + public void setPlace(Integer place) { + this.place = place; + } + + /** + * Generic getter for the virtualMetadataConfiguration property of this class + * @return The virtualMetadataConfiguration property + */ + public VirtualMetadataConfiguration getVirtualMetadataConfiguration() { + return virtualMetadataConfiguration; + } + + /** + * Generic setter for the virtualMetadataConfiguration property of this class + * @param virtualMetadataConfiguration The VirtualBean to which the + * virtualMetadataConfiguration property will be set to + */ + public void setVirtualMetadataConfiguration(VirtualMetadataConfiguration + virtualMetadataConfiguration) { + this.virtualMetadataConfiguration = virtualMetadataConfiguration; + } + + /** + * Generic setter for the useForPlace property + * @param useForPlace The boolean value that the useForPlace property will be set to + */ + public void setUseForPlace(boolean useForPlace) { + this.useForPlace = useForPlace; + } + + /** + * Generic getter for the useForPlace property + * @return The useForPlace to be used by this bean + */ + public boolean getUseForPlace() { + return useForPlace; + } + + /** + * This method will find the correct Relationship from the given item to retrieve the other item from it + * and pass this along to the next VirtualBean that's stored in this class. + * @param context The relevant DSpace context + * @param item The item that will be used to find the related item through its relationships + * @return The String value of the metadata fields concatened with a seperator as defined + * in the deepest Concatened bean in the chain + * Will return an empty list if no relationships are found + * @throws SQLException If something goes wrong + */ + public List getValues(Context context, Item item) throws SQLException { + Entity entity = entityService.findByItemId(context, item.getID()); + EntityType entityType = entityService.getType(context, entity); + + List relationshipTypes = entityService.getAllRelationshipTypes(context, entity); + List possibleRelationshipTypes = new LinkedList<>(); + for (RelationshipType relationshipType : relationshipTypes) { + if (StringUtils.equals(relationshipType.getLeftLabel(), relationshipTypeString) || StringUtils + .equals(relationshipType.getRightLabel(), relationshipTypeString)) { + possibleRelationshipTypes.add(relationshipType); + } + } + + List relationships = new LinkedList<>(); + for (RelationshipType relationshipType : possibleRelationshipTypes) { + relationships.addAll(relationshipService.findByItemAndRelationshipType(context, item, relationshipType)); + } + + for (Relationship relationship : relationships) { + if (relationship.getRelationshipType().getLeftType() == entityType) { + if (relationship.getLeftPlace() == place) { + Item otherItem = relationship.getRightItem(); + return virtualMetadataConfiguration.getValues(context, otherItem); + } + } else if (relationship.getRelationshipType().getRightType() == entityType) { + if (relationship.getRightPlace() == place) { + Item otherItem = relationship.getLeftItem(); + return virtualMetadataConfiguration.getValues(context, otherItem); + } + } + } + + //Return an empty list if no relationships were found + return new LinkedList<>(); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/content/virtual/UUIDValue.java b/dspace-api/src/main/java/org/dspace/content/virtual/UUIDValue.java new file mode 100644 index 0000000000..c5c45cac8c --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/virtual/UUIDValue.java @@ -0,0 +1,41 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content.virtual; + +import java.sql.SQLException; +import java.util.LinkedList; +import java.util.List; + +import org.dspace.content.Item; +import org.dspace.core.Context; + +/** + * This class is used by the VirtualMetadataPopulator. It will simply take the ID of the item that's passed along + * to this and return that as it's value + */ +public class UUIDValue implements VirtualMetadataConfiguration { + + private boolean useForPlace; + + @Override + public List getValues(Context context, Item item) throws SQLException { + List list = new LinkedList<>(); + list.add(String.valueOf(item.getID())); + return list; + } + + @Override + public void setUseForPlace(boolean useForPlace) { + this.useForPlace = useForPlace; + } + + @Override + public boolean getUseForPlace() { + return useForPlace; + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/virtual/VirtualMetadataConfiguration.java b/dspace-api/src/main/java/org/dspace/content/virtual/VirtualMetadataConfiguration.java new file mode 100644 index 0000000000..0cbb685c96 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/virtual/VirtualMetadataConfiguration.java @@ -0,0 +1,45 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content.virtual; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.content.Item; +import org.dspace.core.Context; + +/** + * This interface describes beans to be used for the {@link VirtualMetadataPopulator} implementation. + * The config is located in core-services.xml whilst the actual code implementation is located in + * {@link org.dspace.content.ItemServiceImpl} + */ +public interface VirtualMetadataConfiguration { + + /** + * This method will return a list filled with String values which will be determine by the bean that's responsible + * of handling the metadata fields when fully traversed through all the {@link Related} beans + * @param context The relevant DSpace context + * @param item The item that will be used to either retrieve metadata values from or to find + * the related item through its relationships + * @return The list of String values of all the metadata values as constructed by the responsible bean + * @throws SQLException If something goes wrong + */ + List getValues(Context context, Item item) throws SQLException; + + /** + * Generic setter for the useForPlace property + * @param useForPlace The boolean value that the useForPlace property will be set to + */ + void setUseForPlace(boolean useForPlace); + + /** + * Generic getter for the useForPlace property + * @return The useForPlace to be used by this bean + */ + boolean getUseForPlace(); +} diff --git a/dspace-api/src/main/java/org/dspace/content/virtual/VirtualMetadataPopulator.java b/dspace-api/src/main/java/org/dspace/content/virtual/VirtualMetadataPopulator.java new file mode 100644 index 0000000000..94545ab9fe --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/virtual/VirtualMetadataPopulator.java @@ -0,0 +1,68 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content.virtual; + +import java.util.HashMap; +import java.util.Map; + +import org.dspace.content.RelationshipType; + +/** + * This class is responsible for holding the representation of how a certain relationshipType label has to be + * translated to the virtual metadata added onto the items that belong to the relationships that these + * relationshipTypes belong to + */ +public class VirtualMetadataPopulator { + + /** + * The map that holds this representation + */ + private Map> map; + + /** + * Standard setter for the map + * @param map The map to be used in the VirtualMetadataPopulator + */ + public void setMap(Map> map) { + this.map = map; + } + + /** + * Standard getter for the map + * @return The map that is used in the VirtualMetadataPopulator + */ + public Map> getMap() { + return map; + } + + /** + * This method will return a boolean indicating whether the useForPlace is true or false for the given + * RelationshipType for the left or right label as indicated by the second parameter. + * @param relationshipType The relationshipType for which this should be checked + * @param isLeft The boolean indicating whether to check the left or the right label + * @return A boolean indicating whether the useForPlace is true or not for the given parameters + */ + public boolean isUseForPlaceTrueForRelationshipType(RelationshipType relationshipType, boolean isLeft) { + HashMap hashMaps; + if (isLeft) { + hashMaps = this.getMap().get(relationshipType.getLeftLabel()); + } else { + hashMaps = this.getMap().get(relationshipType.getRightLabel()); + } + if (hashMaps != null) { + for (Map.Entry entry : hashMaps.entrySet()) { + VirtualMetadataConfiguration virtualBean = entry.getValue(); + boolean useForPlace = virtualBean.getUseForPlace(); + if (useForPlace) { + return true; + } + } + } + return false; + } +} diff --git a/dspace-api/src/main/java/org/dspace/core/Constants.java b/dspace-api/src/main/java/org/dspace/core/Constants.java index a73b2d9634..9e4a8db840 100644 --- a/dspace-api/src/main/java/org/dspace/core/Constants.java +++ b/dspace-api/src/main/java/org/dspace/core/Constants.java @@ -244,6 +244,7 @@ public class Constants { public static final String DEFAULT_ENCODING = "UTF-8"; + public static final String VIRTUAL_AUTHORITY_PREFIX = "virtual::"; /** * Default constructor */ diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java index f0e26238ef..d22a0b1539 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceImpl.java @@ -34,8 +34,6 @@ import java.util.TimeZone; import java.util.UUID; import java.util.Vector; -import com.google.common.collect.ImmutableList; - import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.collections4.Transformer; @@ -473,7 +471,8 @@ public class SolrServiceImpl implements SearchService, IndexingService { switch (type) { case Constants.ITEM: Iterator items = itemService.findAllUnfiltered(context); - for (Item item : ImmutableList.copyOf(items)) { + while (items.hasNext()) { + Item item = items.next(); indexContent(context, item, force); //To prevent memory issues, discard an object from the cache after processing context.uncacheEntity(item); diff --git a/dspace-api/src/main/java/org/dspace/embargo/EmbargoServiceImpl.java b/dspace-api/src/main/java/org/dspace/embargo/EmbargoServiceImpl.java index 69c601034a..5ec83c7cdb 100644 --- a/dspace-api/src/main/java/org/dspace/embargo/EmbargoServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/embargo/EmbargoServiceImpl.java @@ -17,7 +17,7 @@ import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DCDate; import org.dspace.content.Item; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.MetadataValue; import org.dspace.content.service.ItemService; import org.dspace.core.Context; @@ -153,8 +153,8 @@ public class EmbargoServiceImpl implements EmbargoService { itemService.clearMetadata(context, item, lift_schema, lift_element, lift_qualifier, Item.ANY); // set the dc.date.available value to right now - itemService.clearMetadata(context, item, MetadataSchema.DC_SCHEMA, "date", "available", Item.ANY); - itemService.addMetadata(context, item, MetadataSchema.DC_SCHEMA, "date", "available", null, + itemService.clearMetadata(context, item, MetadataSchemaEnum.DC.getName(), "date", "available", Item.ANY); + itemService.addMetadata(context, item, MetadataSchemaEnum.DC.getName(), "date", "available", null, DCDate.getCurrent().toString()); log.info("Lifting embargo on Item " + item.getHandle()); diff --git a/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java b/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java index d7174e7d45..f8c5e6268e 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java +++ b/dspace-api/src/main/java/org/dspace/eperson/SubscribeCLITool.java @@ -30,7 +30,7 @@ import org.apache.logging.log4j.Logger; import org.dspace.content.Collection; import org.dspace.content.DCDate; import org.dspace.content.Item; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.MetadataValue; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.ItemService; @@ -228,7 +228,7 @@ public class SubscribeCLITool { } List authors = itemService - .getMetadata(hii.item, MetadataSchema.DC_SCHEMA, "contributor", Item.ANY, Item.ANY); + .getMetadata(hii.item, MetadataSchemaEnum.DC.getName(), "contributor", Item.ANY, Item.ANY); if (authors.size() > 0) { emailText.append("\n ").append(labels.getString("org.dspace.eperson.Subscribe.authors")) diff --git a/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java index 2ae6ff63cb..9e34fd94da 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/HandleIdentifierProvider.java @@ -15,7 +15,7 @@ import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.MetadataValue; import org.dspace.content.service.ItemService; import org.dspace.core.ConfigurationManager; @@ -228,14 +228,15 @@ public class HandleIdentifierProvider extends IdentifierProvider { // First check that identifier doesn't already exist. boolean identifierExists = false; List identifiers = itemService - .getMetadata(item, MetadataSchema.DC_SCHEMA, "identifier", "uri", Item.ANY); + .getMetadata(item, MetadataSchemaEnum.DC.getName(), "identifier", "uri", Item.ANY); for (MetadataValue identifier : identifiers) { if (handleref.equals(identifier.getValue())) { identifierExists = true; } } if (!identifierExists) { - itemService.addMetadata(context, item, MetadataSchema.DC_SCHEMA, "identifier", "uri", null, handleref); + itemService.addMetadata(context, item, MetadataSchemaEnum.DC.getName(), + "identifier", "uri", null, handleref); } } } diff --git a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProvider.java b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProvider.java index f507348279..801b13be15 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProvider.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProvider.java @@ -19,7 +19,7 @@ import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.MetadataValue; import org.dspace.content.service.ItemService; import org.dspace.core.ConfigurationManager; @@ -417,9 +417,9 @@ public class VersionedHandleIdentifierProvider extends IdentifierProvider { // load all identifiers, clear the metadata field, re add all // identifiers which are not from type handle and add the new handle. List identifiers = itemService.getMetadata(item, - MetadataSchema.DC_SCHEMA, "identifier", "uri", + MetadataSchemaEnum.DC.getName(), "identifier", "uri", Item.ANY); - itemService.clearMetadata(context, item, MetadataSchema.DC_SCHEMA, + itemService.clearMetadata(context, item, MetadataSchemaEnum.DC.getName(), "identifier", "uri", Item.ANY); for (MetadataValue identifier : identifiers) { if (this.supports(identifier.getValue())) { @@ -439,7 +439,8 @@ public class VersionedHandleIdentifierProvider extends IdentifierProvider { // Add handle as identifier.uri DC value. if (StringUtils.isNotBlank(handleref)) { - itemService.addMetadata(context, item, MetadataSchema.DC_SCHEMA, "identifier", "uri", null, handleref); + itemService.addMetadata(context, item, MetadataSchemaEnum.DC.getName(), + "identifier", "uri", null, handleref); } itemService.update(context, item); } diff --git a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java index 797358262e..d644c87367 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java +++ b/dspace-api/src/main/java/org/dspace/identifier/VersionedHandleIdentifierProviderWithCanonicalHandles.java @@ -17,7 +17,7 @@ import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.MetadataValue; import org.dspace.content.service.ItemService; import org.dspace.core.ConfigurationManager; @@ -493,8 +493,8 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident // identifiers which are not from type handle and add the new handle. String handleref = handleService.getCanonicalForm(handle); List identifiers = itemService - .getMetadata(item, MetadataSchema.DC_SCHEMA, "identifier", "uri", Item.ANY); - itemService.clearMetadata(context, item, MetadataSchema.DC_SCHEMA, "identifier", "uri", Item.ANY); + .getMetadata(item, MetadataSchemaEnum.DC.getName(), "identifier", "uri", Item.ANY); + itemService.clearMetadata(context, item, MetadataSchemaEnum.DC.getName(), "identifier", "uri", Item.ANY); for (MetadataValue identifier : identifiers) { if (this.supports(identifier.getValue())) { // ignore handles @@ -509,7 +509,8 @@ public class VersionedHandleIdentifierProviderWithCanonicalHandles extends Ident identifier.getConfidence()); } if (!StringUtils.isEmpty(handleref)) { - itemService.addMetadata(context, item, MetadataSchema.DC_SCHEMA, "identifier", "uri", null, handleref); + itemService.addMetadata(context, item, MetadataSchemaEnum.DC.getName(), + "identifier", "uri", null, handleref); } itemService.update(context, item); } diff --git a/dspace-api/src/main/java/org/dspace/rdf/conversion/MetadataConverterPlugin.java b/dspace-api/src/main/java/org/dspace/rdf/conversion/MetadataConverterPlugin.java index 36501b89c4..72ba03d99d 100644 --- a/dspace-api/src/main/java/org/dspace/rdf/conversion/MetadataConverterPlugin.java +++ b/dspace-api/src/main/java/org/dspace/rdf/conversion/MetadataConverterPlugin.java @@ -33,6 +33,7 @@ import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.MetadataField; import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.MetadataValue; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.DSpaceObjectService; @@ -133,7 +134,7 @@ public class MetadataConverterPlugin implements ConverterPlugin { } List metadata_values = dsoService - .getMetadata(dso, MetadataSchema.DC_SCHEMA, Item.ANY, Item.ANY, Item.ANY); + .getMetadata(dso, MetadataSchemaEnum.DC.getName(), Item.ANY, Item.ANY, Item.ANY); for (MetadataValue value : metadata_values) { MetadataField metadataField = value.getMetadataField(); MetadataSchema metadataSchema = metadataField.getMetadataSchema(); diff --git a/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDataVisits.java b/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDataVisits.java index e4fb04631f..92691b99a8 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDataVisits.java +++ b/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDataVisits.java @@ -354,13 +354,13 @@ public class StatisticsDataVisits extends StatisticsData { /* for (int j = 0; j < topCounts2.length; j++) { ObjectCount count2 = topCounts2[j]; - String query = firsDataset.getFacetField() + ":" + count1.getValue(); + String query = firsDataset.getFacetField() + ":" + count1.getValues(); // Check if we also have a type present (if so this should be put into the query if ("id".equals(firsDataset.getFacetField()) && firsDataset.getQueries().get(0).getDsoType() != -1) query += " AND type:" + firsDataset.getQueries().get(0).getDsoType(); - query += " AND " + secondDataSet.getFacetField() + ":" + count2.getValue(); + query += " AND " + secondDataSet.getFacetField() + ":" + count2.getValues(); // Check if we also have a type present (if so this should be put into the query if ("id".equals(secondDataSet.getFacetField()) && secondDataSet.getQueries().get(0) .getDsoType() != -1) @@ -372,8 +372,8 @@ public class StatisticsDataVisits extends StatisticsData { // No need to add this many times // TODO: dit vervangen door te displayen value if (i == 0) { - dataset.setRowLabel(j, getResultName(count2.getValue(), secondDataSet, context)); - dataset.setRowLabelAttr(j, getAttributes(count2.getValue(), secondDataSet, context)); + dataset.setRowLabel(j, getResultName(count2.getValues(), secondDataSet, context)); + dataset.setRowLabelAttr(j, getAttributes(count2.getValues(), secondDataSet, context)); } diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseRegistryUpdater.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseRegistryUpdater.java index 209dec554b..0f4e92fdda 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseRegistryUpdater.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseRegistryUpdater.java @@ -71,6 +71,13 @@ public class DatabaseRegistryUpdater implements FlywayCallback { MetadataImporter.loadRegistry(base + "dublin-core-types.xml", true); MetadataImporter.loadRegistry(base + "dcterms-types.xml", true); MetadataImporter.loadRegistry(base + "local-types.xml", true); + MetadataImporter.loadRegistry(base + "relationship-formats.xml", true); + MetadataImporter.loadRegistry(base + "person-types.xml", true); + MetadataImporter.loadRegistry(base + "project-types.xml", true); + MetadataImporter.loadRegistry(base + "orgunit-types.xml", true); + MetadataImporter.loadRegistry(base + "journal-types.xml", true); + MetadataImporter.loadRegistry(base + "journalissue-types.xml", true); + MetadataImporter.loadRegistry(base + "journalvolume-types.xml", true); MetadataImporter.loadRegistry(base + "eperson-types.xml", true); MetadataImporter.loadRegistry(base + "sword-metadata.xml", true); @@ -163,6 +170,5 @@ public class DatabaseRegistryUpdater implements FlywayCallback { @Override public void afterInfo(Connection connection) { - } } diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java index f4b0936c6f..cadd3eee52 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java @@ -1190,14 +1190,15 @@ public class DatabaseUtils { *

* Because the DB migration may be initialized by commandline or any one of * the many DSpace webapps, this checks for the existence of a temporary - * file to know when Discovery/Solr needs reindexing. - * - * @return whether reindex flag is true/false + * file, and the discovery.autoReindex setting to know when + * Discovery/Solr needs reindexing. + * @return whether reindexing should happen. */ public static boolean getReindexDiscovery() { - // Simply check if the flag file exists - File reindexFlag = new File(reindexDiscoveryFilePath); - return reindexFlag.exists(); + boolean autoReindex = DSpaceServicesFactory.getInstance() + .getConfigurationService() + .getBooleanProperty("discovery.autoReindex", true); + return (autoReindex && new File(reindexDiscoveryFilePath).exists()); } /** @@ -1222,7 +1223,7 @@ public class DatabaseUtils { /** * Internal class to actually perform re-indexing in a separate thread. - * (See checkReindexDiscovery() method)> + * (See checkReindexDiscovery() method). */ private static class ReindexerThread extends Thread { private final IndexingService indexer; diff --git a/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowServiceImpl.java b/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowServiceImpl.java index 452b1fafd6..6ddd750b20 100644 --- a/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/workflowbasic/BasicWorkflowServiceImpl.java @@ -31,7 +31,7 @@ import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.DCDate; import org.dspace.content.Item; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.WorkspaceItem; import org.dspace.content.service.CollectionService; import org.dspace.content.service.InstallItemService; @@ -910,8 +910,8 @@ public class BasicWorkflowServiceImpl implements BasicWorkflowService { + rejection_message + " on " + now + " (GMT) "; // Add to item as a DC field - itemService - .addMetadata(context, myitem, MetadataSchema.DC_SCHEMA, "description", "provenance", "en", provDescription); + itemService.addMetadata(context, myitem, MetadataSchemaEnum.DC.getName(), + "description", "provenance", "en", provDescription); itemService.update(context, myitem); // convert into personal workspace @@ -1135,8 +1135,8 @@ public class BasicWorkflowServiceImpl implements BasicWorkflowService { provDescription += installItemService.getBitstreamProvenanceMessage(context, item); // Add to item as a DC field - itemService - .addMetadata(context, item, MetadataSchema.DC_SCHEMA, "description", "provenance", "en", provDescription); + itemService.addMetadata(context, item, MetadataSchemaEnum.DC.getName(), + "description", "provenance", "en", provDescription); itemService.update(context, item); } @@ -1163,8 +1163,8 @@ public class BasicWorkflowServiceImpl implements BasicWorkflowService { provmessage += installItemService.getBitstreamProvenanceMessage(context, myitem); // Add message to the DC - itemService - .addMetadata(context, myitem, MetadataSchema.DC_SCHEMA, "description", "provenance", "en", provmessage); + itemService.addMetadata(context, myitem, MetadataSchemaEnum.DC.getName(), + "description", "provenance", "en", provmessage); itemService.update(context, myitem); } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java index 036e0ce3aa..591d5f6b29 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/XmlWorkflowServiceImpl.java @@ -32,7 +32,7 @@ import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.DCDate; import org.dspace.content.Item; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.MetadataValue; import org.dspace.content.WorkspaceItem; import org.dspace.content.service.BitstreamFormatService; @@ -593,7 +593,7 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { // Get title List titles = itemService - .getMetadata(item, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY); + .getMetadata(item, MetadataSchemaEnum.DC.getName(), "title", null, Item.ANY); String title = ""; try { title = I18nUtil.getMessage("org.dspace.workflow.WorkflowManager.untitled"); @@ -891,7 +891,8 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { // Add to item as a DC field itemService - .addMetadata(context, myitem, MetadataSchema.DC_SCHEMA, "description", "provenance", "en", provDescription); + .addMetadata(context, myitem, MetadataSchemaEnum.DC.getName(), + "description", "provenance", "en", provDescription); //Clear any workflow schema related metadata itemService @@ -1024,7 +1025,8 @@ public class XmlWorkflowServiceImpl implements XmlWorkflowService { // Add message to the DC itemService - .addMetadata(context, myitem, MetadataSchema.DC_SCHEMA, "description", "provenance", "en", provmessage); + .addMetadata(context, myitem, MetadataSchemaEnum.DC.getName(), + "description", "provenance", "en", provmessage); itemService.update(context, myitem); } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/AcceptEditRejectAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/AcceptEditRejectAction.java index 4c85986d31..7d330aace9 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/AcceptEditRejectAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/AcceptEditRejectAction.java @@ -13,7 +13,7 @@ import javax.servlet.http.HttpServletRequest; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DCDate; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.core.Context; import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; import org.dspace.xmlworkflow.state.Step; @@ -91,7 +91,7 @@ public class AcceptEditRejectAction extends ProcessingAction { + usersName + " on " + now + " (GMT) "; // Add to item as a DC field - itemService.addMetadata(c, wfi.getItem(), MetadataSchema.DC_SCHEMA, "description", "provenance", "en", + itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), "description", "provenance", "en", provDescription); itemService.update(c, wfi.getItem()); } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/FinalEditAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/FinalEditAction.java index 2b05f3d0d1..50686f3993 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/FinalEditAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/FinalEditAction.java @@ -13,7 +13,7 @@ import javax.servlet.http.HttpServletRequest; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DCDate; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.core.Context; import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; import org.dspace.xmlworkflow.state.Step; @@ -68,7 +68,7 @@ public class FinalEditAction extends ProcessingAction { + usersName + " on " + now + " (GMT) "; // Add to item as a DC field - itemService.addMetadata(c, wfi.getItem(), MetadataSchema.DC_SCHEMA, "description", "provenance", "en", + itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), "description", "provenance", "en", provDescription); itemService.update(c, wfi.getItem()); } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ReviewAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ReviewAction.java index 4fca39a91a..6ccb2c53e6 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ReviewAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ReviewAction.java @@ -14,7 +14,7 @@ import javax.servlet.http.HttpServletRequest; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DCDate; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.core.Context; import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; import org.dspace.xmlworkflow.state.Step; @@ -74,7 +74,7 @@ public class ReviewAction extends ProcessingAction { + usersName + " on " + now + " (GMT) "; // Add to item as a DC field - itemService.addMetadata(c, wfi.getItem(), MetadataSchema.DC_SCHEMA, "description", "provenance", "en", + itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), "description", "provenance", "en", provDescription); itemService.update(c, wfi.getItem()); } diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreEvaluationAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreEvaluationAction.java index 97f26eb3c7..df06c6b0de 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreEvaluationAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/ScoreEvaluationAction.java @@ -14,7 +14,7 @@ import javax.servlet.http.HttpServletRequest; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Item; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.MetadataValue; import org.dspace.core.Context; import org.dspace.workflow.WorkflowException; @@ -66,8 +66,8 @@ public class ScoreEvaluationAction extends ProcessingAction { String provDescription = getProvenanceStartId() + " Approved for entry into archive with a score of: " + scoreMean; - itemService.addMetadata(c, wfi.getItem(), MetadataSchema.DC_SCHEMA, "description", "provenance", "en", - provDescription); + itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), + "description", "provenance", "en", provDescription); itemService.update(c, wfi.getItem()); } if (hasPassed) { diff --git a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java index 74eea2d448..215eaaf645 100644 --- a/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java +++ b/dspace-api/src/main/java/org/dspace/xmlworkflow/state/actions/processingaction/SingleUserReviewAction.java @@ -14,7 +14,7 @@ import javax.servlet.http.HttpServletRequest; import org.dspace.app.util.Util; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DCDate; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.core.Context; import org.dspace.xmlworkflow.factory.XmlWorkflowServiceFactory; import org.dspace.xmlworkflow.state.Step; @@ -91,7 +91,7 @@ public class SingleUserReviewAction extends ProcessingAction { + usersName + " on " + now + " (GMT) "; // Add to item as a DC field - itemService.addMetadata(c, wfi.getItem(), MetadataSchema.DC_SCHEMA, "description", "provenance", "en", + itemService.addMetadata(c, wfi.getItem(), MetadataSchemaEnum.DC.getName(), "description", "provenance", "en", provDescription); itemService.update(c, wfi.getItem()); } diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2018.04.16__dspace-entities.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2018.04.16__dspace-entities.sql new file mode 100644 index 0000000000..1b87eb8ee6 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2018.04.16__dspace-entities.sql @@ -0,0 +1,65 @@ +-- +-- 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/ +-- + +-- =============================================================== +-- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING +-- +-- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED +-- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. +-- http://flywaydb.org/ +-- =============================================================== + +------------------------------------------------------------- +-- This will create the setup for the dspace 7 entities usage +------------------------------------------------------------- +CREATE SEQUENCE entity_type_id_seq; +CREATE SEQUENCE relationship_type_id_seq; +CREATE SEQUENCE relationship_id_seq; + +CREATE TABLE entity_type +( + id INTEGER NOT NULL PRIMARY KEY, + label varchar(32) UNIQUE NOT NULL +); + +CREATE TABLE relationship_type +( + id INTEGER NOT NULL PRIMARY KEY, + left_type INTEGER NOT NULL, + right_type INTEGER NOT NULL, + left_label varchar(32) NOT NULL, + right_label varchar(32) NOT NULL, + left_min_cardinality INTEGER, + left_max_cardinality INTEGER, + right_min_cardinality INTEGER, + right_max_cardinality INTEGER, + FOREIGN KEY (left_type) REFERENCES entity_type(id), + FOREIGN KEY (right_type) REFERENCES entity_type(id), + CONSTRAINT u_relationship_type_constraint UNIQUE (left_type, right_type, left_label, right_label) + +); + +CREATE TABLE relationship +( + id INTEGER NOT NULL PRIMARY KEY, + left_id uuid NOT NULL REFERENCES item(uuid), + type_id INTEGER NOT NULL REFERENCES relationship_type(id), + right_id uuid NOT NULL REFERENCES item(uuid), + left_place INTEGER, + right_place INTEGER, + CONSTRAINT u_constraint UNIQUE (left_id, type_id, right_id) + +); + +CREATE INDEX entity_type_label_idx ON entity_type(label); +CREATE INDEX relationship_type_by_left_type_idx ON relationship_type(left_type); +CREATE INDEX relationship_type_by_right_type_idx ON relationship_type(right_type); +CREATE INDEX relationship_type_by_left_label_idx ON relationship_type(left_label); +CREATE INDEX relationship_type_by_right_label_idx ON relationship_type(right_label); +CREATE INDEX relationship_by_left_id_idx ON relationship(left_id); +CREATE INDEX relationship_by_right_id_idx ON relationship(right_id); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.04.16__dspace-entities.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.04.16__dspace-entities.sql new file mode 100644 index 0000000000..68855dc2dc --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2018.04.16__dspace-entities.sql @@ -0,0 +1,65 @@ +-- +-- 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/ +-- + +-- =============================================================== +-- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING +-- +-- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED +-- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. +-- http://flywaydb.org/ +-- =============================================================== + +------------------------------------------------------------- +-- This will create the setup for the dspace 7 entities usage +------------------------------------------------------------- +CREATE SEQUENCE entity_type_id_seq; +CREATE SEQUENCE relationship_type_id_seq; +CREATE SEQUENCE relationship_id_seq; + +CREATE TABLE entity_type +( + id INTEGER NOT NULL PRIMARY KEY, + label varchar(32) UNIQUE NOT NULL +); + +CREATE TABLE relationship_type +( + id INTEGER NOT NULL PRIMARY KEY, + left_type INTEGER NOT NULL, + right_type INTEGER NOT NULL, + left_label varchar(32) NOT NULL, + right_label varchar(32) NOT NULL, + left_min_cardinality INTEGER, + left_max_cardinality INTEGER, + right_min_cardinality INTEGER, + right_max_cardinality INTEGER, + FOREIGN KEY (left_type) REFERENCES entity_type(id), + FOREIGN KEY (right_type) REFERENCES entity_type(id), + CONSTRAINT u_relationship_type_constraint UNIQUE (left_type, right_type, left_label, right_label) + +); + +CREATE TABLE relationship +( + id INTEGER NOT NULL PRIMARY KEY, + left_id uuid NOT NULL REFERENCES item(uuid), + type_id INTEGER NOT NULL REFERENCES relationship_type(id), + right_id uuid NOT NULL REFERENCES item(uuid), + left_place INTEGER, + right_place INTEGER, + CONSTRAINT u_constraint UNIQUE (left_id, type_id, right_id) + +); + +CREATE INDEX entity_type_label_idx ON entity_type(label); +CREATE INDEX relationship_type_by_left_type_idx ON relationship_type(left_type); +CREATE INDEX relationship_type_by_right_type_idx ON relationship_type(right_type); +CREATE INDEX relationship_type_by_left_label_idx ON relationship_type(left_label); +CREATE INDEX relationship_type_by_right_label_idx ON relationship_type(right_label); +CREATE INDEX relationship_by_left_id_idx ON relationship(left_id); +CREATE INDEX relationship_by_right_id_idx ON relationship(right_id); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2018.04.16__dspace-entities.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2018.04.16__dspace-entities.sql new file mode 100644 index 0000000000..3a80b7a5fa --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2018.04.16__dspace-entities.sql @@ -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/ +-- + +-- =============================================================== +-- WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING +-- +-- DO NOT MANUALLY RUN THIS DATABASE MIGRATION. IT WILL BE EXECUTED +-- AUTOMATICALLY (IF NEEDED) BY "FLYWAY" WHEN YOU STARTUP DSPACE. +-- http://flywaydb.org/ +-- =============================================================== + +------------------------------------------------------------- +-- This will create the setup for the dspace 7 entities usage +------------------------------------------------------------- +CREATE SEQUENCE entity_type_id_seq; +CREATE SEQUENCE relationship_type_id_seq; +CREATE SEQUENCE relationship_id_seq; + +CREATE TABLE entity_type +( + id INTEGER NOT NULL PRIMARY KEY, + label varchar(32) UNIQUE NOT NULL +); + +CREATE TABLE relationship_type +( + id INTEGER NOT NULL PRIMARY KEY, + left_type INTEGER NOT NULL, + right_type INTEGER NOT NULL, + left_label varchar(32) NOT NULL, + right_label varchar(32) NOT NULL, + left_min_cardinality INTEGER, + left_max_cardinality INTEGER, + right_min_cardinality INTEGER, + right_max_cardinality INTEGER, + FOREIGN KEY (left_type) REFERENCES entity_type(id), + FOREIGN KEY (right_type) REFERENCES entity_type(id), + CONSTRAINT u_relationship_type_constraint UNIQUE (left_type, right_type, left_label, right_label) +); + +CREATE TABLE relationship +( + id INTEGER NOT NULL PRIMARY KEY, + left_id uuid NOT NULL REFERENCES item(uuid), + type_id INTEGER NOT NULL REFERENCES relationship_type(id), + right_id uuid NOT NULL REFERENCES item(uuid), + left_place INTEGER, + right_place INTEGER, + CONSTRAINT u_constraint UNIQUE (left_id, type_id, right_id) + +); + +CREATE INDEX entity_type_label_idx ON entity_type(label); +CREATE INDEX relationship_type_by_left_type_idx ON relationship_type(left_type); +CREATE INDEX relationship_type_by_right_type_idx ON relationship_type(right_type); +CREATE INDEX relationship_type_by_left_label_idx ON relationship_type(left_label); +CREATE INDEX relationship_type_by_right_label_idx ON relationship_type(right_label); +CREATE INDEX relationship_by_left_id_idx ON relationship(left_id); +CREATE INDEX relationship_by_right_id_idx ON relationship(right_id); \ No newline at end of file diff --git a/dspace-api/src/test/data/dspaceFolder/config/entities/relationship-types-update.xml b/dspace-api/src/test/data/dspaceFolder/config/entities/relationship-types-update.xml new file mode 100644 index 0000000000..217d898f94 --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/config/entities/relationship-types-update.xml @@ -0,0 +1,127 @@ + + + + + + Publication + Person + isAuthorOfPublication + isPublicationOfAuthor + + 10 + + + 0 + + + + Publication + Project + isProjectOfPublication + isPublicationOfProject + + 0 + + + 0 + + + + Publication + OrgUnit + isOrgUnitOfPublication + isPublicationOfOrgUnit + + 0 + + + 0 + + + + Person + Project + isProjectOfPerson + isPersonOfProject + + 0 + + + 0 + + + + Person + OrgUnit + isOrgUnitOfPerson + isPersonOfOrgUnit + + 0 + + + 0 + + + + Project + OrgUnit + isOrgUnitOfProject + isProjectOfOrgUnit + + 0 + + + 0 + + + + Journal + JournalVolume + isVolumeOfJournal + isJournalOfVolume + + 0 + + + 1 + + + + JournalVolume + JournalIssue + isIssueOfJournalVolume + isJournalVolumeOfIssue + + 0 + + + 1 + 1 + + + + Publication + OrgUnit + isAuthorOfPublication + isPublicationOfAuthor + + 0 + + + 0 + + + + JournalIssue + Publication + isPublicationOfJournalIssue + isJournalIssueOfPublication + + 0 + + + 0 + 1 + + + \ No newline at end of file diff --git a/dspace-api/src/test/data/dspaceFolder/config/entities/relationship-types.dtd b/dspace-api/src/test/data/dspaceFolder/config/entities/relationship-types.dtd new file mode 100644 index 0000000000..021ad2267c --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/config/entities/relationship-types.dtd @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/entities/relationship-types.xml b/dspace-api/src/test/data/dspaceFolder/config/entities/relationship-types.xml new file mode 100644 index 0000000000..0a0d70c929 --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/config/entities/relationship-types.xml @@ -0,0 +1,127 @@ + + + + + + Publication + Person + isAuthorOfPublication + isPublicationOfAuthor + + 0 + + + 0 + + + + Publication + Project + isProjectOfPublication + isPublicationOfProject + + 0 + + + 0 + + + + Publication + OrgUnit + isOrgUnitOfPublication + isPublicationOfOrgUnit + + 0 + + + 0 + + + + Person + Project + isProjectOfPerson + isPersonOfProject + + 0 + + + 0 + + + + Person + OrgUnit + isOrgUnitOfPerson + isPersonOfOrgUnit + + 0 + + + 0 + + + + Project + OrgUnit + isOrgUnitOfProject + isProjectOfOrgUnit + + 0 + + + 0 + + + + Journal + JournalVolume + isVolumeOfJournal + isJournalOfVolume + + 0 + + + 1 + + + + JournalVolume + JournalIssue + isIssueOfJournalVolume + isJournalVolumeOfIssue + + 0 + + + 1 + 1 + + + + Publication + OrgUnit + isAuthorOfPublication + isPublicationOfAuthor + + 0 + + + 0 + + + + JournalIssue + Publication + isPublicationOfJournalIssue + isJournalIssueOfPublication + + 0 + + + 0 + 1 + + + \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/content/CollectionTest.java b/dspace-api/src/test/java/org/dspace/content/CollectionTest.java index 393d221923..1e6c7f553a 100644 --- a/dspace-api/src/test/java/org/dspace/content/CollectionTest.java +++ b/dspace-api/src/test/java/org/dspace/content/CollectionTest.java @@ -1838,16 +1838,16 @@ public class CollectionTest extends AbstractDSpaceObjectTest { public void testGetCommunities() throws Exception { context.turnOffAuthorisationSystem(); Community community = communityService.create(null, context); - communityService.setMetadataSingleValue(context, community, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY, - "community 3"); + communityService.setMetadataSingleValue(context, community, MetadataSchemaEnum.DC.getName(), + "title", null, Item.ANY, "community 3"); this.collection.addCommunity(community); community = communityService.create(null, context); - communityService.setMetadataSingleValue(context, community, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY, - "community 1"); + communityService.setMetadataSingleValue(context, community, MetadataSchemaEnum.DC.getName(), + "title", null, Item.ANY, "community 1"); this.collection.addCommunity(community); community = communityService.create(null, context); - communityService.setMetadataSingleValue(context, community, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY, - "community 2"); + communityService.setMetadataSingleValue(context, community, MetadataSchemaEnum.DC.getName(), + "title", null, Item.ANY, "community 2"); this.collection.addCommunity(community); context.restoreAuthSystemState(); assertTrue("testGetCommunities 0", collection.getCommunities().size() == 4); diff --git a/dspace-api/src/test/java/org/dspace/content/CommunityTest.java b/dspace-api/src/test/java/org/dspace/content/CommunityTest.java index e15e567520..b1b9f99623 100644 --- a/dspace-api/src/test/java/org/dspace/content/CommunityTest.java +++ b/dspace-api/src/test/java/org/dspace/content/CommunityTest.java @@ -673,14 +673,17 @@ public class CommunityTest extends AbstractDSpaceObjectTest { context.turnOffAuthorisationSystem(); Collection collection = collectionService.create(context, c); - collectionService.setMetadataSingleValue(context, collection, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY, - "collection B"); + collectionService + .setMetadataSingleValue(context, collection, MetadataSchemaEnum.DC.getName(), + "title", null, Item.ANY, "collection B"); collection = collectionService.create(context, c); - collectionService.setMetadataSingleValue(context, collection, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY, - "collection C"); + collectionService + .setMetadataSingleValue(context, collection, MetadataSchemaEnum.DC.getName(), + "title", null, Item.ANY, "collection C"); collection = collectionService.create(context, c); - collectionService.setMetadataSingleValue(context, collection, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY, - "collection A"); + collectionService + .setMetadataSingleValue(context, collection, MetadataSchemaEnum.DC.getName(), + "title", null, Item.ANY, "collection A"); //we need to commit the changes so we don't block the table for testing context.restoreAuthSystemState(); @@ -712,14 +715,17 @@ public class CommunityTest extends AbstractDSpaceObjectTest { context.turnOffAuthorisationSystem(); Community community = communityService.create(c, context); - communityService.setMetadataSingleValue(context, community, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY, - "subcommunity B"); + communityService + .setMetadataSingleValue(context, community, MetadataSchemaEnum.DC.getName(), + "title", null, Item.ANY, "subcommunity B"); community = communityService.create(c, context); - communityService.setMetadataSingleValue(context, community, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY, - "subcommunity A"); + communityService + .setMetadataSingleValue(context, community, MetadataSchemaEnum.DC.getName(), + "title", null, Item.ANY, "subcommunity A"); community = communityService.create(c, context); - communityService.setMetadataSingleValue(context, community, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY, - "subcommunity C"); + communityService + .setMetadataSingleValue(context, community, MetadataSchemaEnum.DC.getName(), + "title", null, Item.ANY, "subcommunity C"); //we need to commit the changes so we don't block the table for testing context.restoreAuthSystemState(); diff --git a/dspace-api/src/test/java/org/dspace/content/ItemTest.java b/dspace-api/src/test/java/org/dspace/content/ItemTest.java index c337b1e46b..63b012b00e 100644 --- a/dspace-api/src/test/java/org/dspace/content/ItemTest.java +++ b/dspace-api/src/test/java/org/dspace/content/ItemTest.java @@ -737,12 +737,12 @@ public class ItemTest extends AbstractDSpaceObjectTest { public void testGetCollections() throws Exception { context.turnOffAuthorisationSystem(); Collection collection = collectionService.create(context, owningCommunity); - collectionService.setMetadataSingleValue(context, collection, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY, - "collection B"); + collectionService.setMetadataSingleValue(context, collection, MetadataSchemaEnum.DC.getName(), + "title", null, Item.ANY, "collection B"); it.addCollection(collection); collection = collectionService.create(context, owningCommunity); - collectionService.setMetadataSingleValue(context, collection, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY, - "collection A"); + collectionService.setMetadataSingleValue(context, collection, MetadataSchemaEnum.DC.getName(), + "title", null, Item.ANY, "collection A"); it.addCollection(collection); context.restoreAuthSystemState(); assertThat("testGetCollections 0", it.getCollections(), notNullValue()); diff --git a/dspace-api/src/test/java/org/dspace/content/MetadataFieldTest.java b/dspace-api/src/test/java/org/dspace/content/MetadataFieldTest.java index 55c5315bb0..edd030a135 100644 --- a/dspace-api/src/test/java/org/dspace/content/MetadataFieldTest.java +++ b/dspace-api/src/test/java/org/dspace/content/MetadataFieldTest.java @@ -78,14 +78,14 @@ public class MetadataFieldTest extends AbstractUnitTest { public void init() { super.init(); try { - this.dcSchema = metadataSchemaService.find(context, MetadataSchema.DC_SCHEMA); + this.dcSchema = metadataSchemaService.find(context, MetadataSchemaEnum.DC.getName()); this.mf = metadataFieldService.findByElement(context, - MetadataSchema.DC_SCHEMA, element, qualifier); + MetadataSchemaEnum.DC.getName(), element, qualifier); if (mf == null) { context.turnOffAuthorisationSystem(); this.mf = metadataFieldService - .create(context, metadataSchemaService.find(context, MetadataSchema.DC_SCHEMA), element, qualifier, - scopeNote); + .create(context, metadataSchemaService.find(context, MetadataSchemaEnum.DC.getName()), + element, qualifier, scopeNote); context.restoreAuthSystemState(); } @@ -165,7 +165,7 @@ public class MetadataFieldTest extends AbstractUnitTest { */ @Test public void testGetSchema() { - assertThat("testGetSchemaID 0", mf.getMetadataSchema().getName(), equalTo(MetadataSchema.DC_SCHEMA)); + assertThat("testGetSchemaID 0", mf.getMetadataSchema().getName(), equalTo(MetadataSchemaEnum.DC.getName())); } /** @@ -259,7 +259,8 @@ public class MetadataFieldTest extends AbstractUnitTest { */ @Test public void testFindByElement() throws Exception { - MetadataField found = metadataFieldService.findByElement(context, MetadataSchema.DC_SCHEMA, element, qualifier); + MetadataField found = metadataFieldService.findByElement(context, MetadataSchemaEnum.DC.getName(), + element, qualifier); assertThat("testFindByElement 0", found, notNullValue()); assertThat("testFindByElement 1", found.getID(), equalTo(mf.getID())); assertThat("testFindByElement 2", found.getElement(), equalTo(mf.getElement())); @@ -290,7 +291,7 @@ public class MetadataFieldTest extends AbstractUnitTest { @Test public void testFindAllInSchema() throws Exception { List found = metadataFieldService - .findAllInSchema(context, metadataSchemaService.find(context, MetadataSchema.DC_SCHEMA)); + .findAllInSchema(context, metadataSchemaService.find(context, MetadataSchemaEnum.DC.getName())); assertThat("testFindAllInSchema 0", found, notNullValue()); assertTrue("testFindAllInSchema 1", found.size() >= 1); assertTrue("testFindAllInSchema 2", found.size() <= metadataFieldService.findAll(context).size()); @@ -320,7 +321,7 @@ public class MetadataFieldTest extends AbstractUnitTest { MetadataField m = metadataFieldService.create(context, dcSchema, elem, qual, null); metadataFieldService.update(context, m); - MetadataField found = metadataFieldService.findByElement(context, MetadataSchema.DC_SCHEMA, elem, qual); + MetadataField found = metadataFieldService.findByElement(context, MetadataSchemaEnum.DC.getName(), elem, qual); assertThat("testUpdateAuth 0", found.getID(), equalTo(m.getID())); } @@ -380,7 +381,7 @@ public class MetadataFieldTest extends AbstractUnitTest { metadataFieldService.delete(context, m); - MetadataField found = metadataFieldService.findByElement(context, MetadataSchema.DC_SCHEMA, elem, qual); + MetadataField found = metadataFieldService.findByElement(context, MetadataSchemaEnum.DC.getName(), elem, qual); assertThat("testDeleteAuth 0", found, nullValue()); } diff --git a/dspace-api/src/test/java/org/dspace/content/MetadataSchemaTest.java b/dspace-api/src/test/java/org/dspace/content/MetadataSchemaTest.java index 13f7468543..30a1a07325 100644 --- a/dspace-api/src/test/java/org/dspace/content/MetadataSchemaTest.java +++ b/dspace-api/src/test/java/org/dspace/content/MetadataSchemaTest.java @@ -59,7 +59,7 @@ public class MetadataSchemaTest extends AbstractUnitTest { public void init() { super.init(); try { - this.ms = metadataSchemaService.find(context, MetadataSchema.DC_SCHEMA); + this.ms = metadataSchemaService.find(context, MetadataSchemaEnum.DC.getName()); } catch (SQLException ex) { log.error("SQL Error in init", ex); fail("SQL Error in init: " + ex.getMessage()); @@ -122,7 +122,7 @@ public class MetadataSchemaTest extends AbstractUnitTest { @Test public void testGetSchemaID() throws SQLException { assertThat("testGetSchemaID 0", ms.getID(), - equalTo(metadataSchemaService.find(context, MetadataSchema.DC_SCHEMA).getID())); + equalTo(metadataSchemaService.find(context, MetadataSchemaEnum.DC.getName()).getID())); } /** diff --git a/dspace-api/src/test/java/org/dspace/content/MetadataValueTest.java b/dspace-api/src/test/java/org/dspace/content/MetadataValueTest.java index 947720ef87..808aab46ec 100644 --- a/dspace-api/src/test/java/org/dspace/content/MetadataValueTest.java +++ b/dspace-api/src/test/java/org/dspace/content/MetadataValueTest.java @@ -95,7 +95,7 @@ public class MetadataValueTest extends AbstractUnitTest { this.it = installItemService.installItem(context, workspaceItem); this.mf = metadataFieldService.findByElement(context, - MetadataSchema.DC_SCHEMA, element, qualifier); + MetadataSchemaEnum.DC.getName(), element, qualifier); this.mv = metadataValueService.create(context, it, mf); context.restoreAuthSystemState(); } catch (AuthorizeException ex) { diff --git a/dspace-api/src/test/java/org/dspace/content/packager/ITDSpaceAIP.java b/dspace-api/src/test/java/org/dspace/content/packager/ITDSpaceAIP.java index 963da2bfab..f2c8c8ac1b 100644 --- a/dspace-api/src/test/java/org/dspace/content/packager/ITDSpaceAIP.java +++ b/dspace-api/src/test/java/org/dspace/content/packager/ITDSpaceAIP.java @@ -36,7 +36,7 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.WorkspaceItem; import org.dspace.content.crosswalk.CrosswalkException; import org.dspace.content.factory.ContentServiceFactory; @@ -164,17 +164,18 @@ public class ITDSpaceAIP extends AbstractUnitTest { // Community topCommunity = communityService.create(null, context); communityService - .addMetadata(context, topCommunity, MetadataSchema.DC_SCHEMA, "title", null, null, "Top Community"); + .addMetadata(context, topCommunity, MetadataSchemaEnum.DC.getName(), + "title", null, null, "Top Community"); communityService.update(context, topCommunity); topCommunityHandle = topCommunity.getHandle(); Community child = communityService.createSubcommunity(context, topCommunity); communityService - .addMetadata(context, child, MetadataSchema.DC_SCHEMA, "title", null, null, "Child Community"); + .addMetadata(context, child, MetadataSchemaEnum.DC.getName(), "title", null, null, "Child Community"); communityService.update(context, child); Community grandchild = communityService.createSubcommunity(context, child); - communityService.addMetadata(context, grandchild, MetadataSchema.DC_SCHEMA, "title", null, null, + communityService.addMetadata(context, grandchild, MetadataSchemaEnum.DC.getName(), "title", null, null, "Grandchild Community"); communityService.update(context, grandchild); @@ -558,8 +559,9 @@ public class ITDSpaceAIP extends AbstractUnitTest { // Change the Community name String newName = "This is NOT my Community name!"; - communityService.clearMetadata(context, topCommunity, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY); - communityService.addMetadata(context, topCommunity, MetadataSchema.DC_SCHEMA, "title", null, null, newName); + communityService.clearMetadata(context, topCommunity, MetadataSchemaEnum.DC.getName(), "title", null, Item.ANY); + communityService.addMetadata(context, topCommunity, MetadataSchemaEnum.DC.getName(), + "title", null, null, newName); // Ensure name is changed assertEquals("testReplaceCommunityOnly() new name", topCommunity.getName(), newName); @@ -781,8 +783,10 @@ public class ITDSpaceAIP extends AbstractUnitTest { // Change the Collection name String newName = "This is NOT my Collection name!"; - collectionService.clearMetadata(context, testCollection, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY); - collectionService.addMetadata(context, testCollection, MetadataSchema.DC_SCHEMA, "title", null, null, newName); + collectionService.clearMetadata(context, testCollection, MetadataSchemaEnum.DC.getName(), + "title", null, Item.ANY); + collectionService.addMetadata(context, testCollection, MetadataSchemaEnum.DC.getName(), + "title", null, null, newName); // Ensure name is changed assertEquals("testReplaceCollectionOnly() new name", testCollection.getName(), newName); @@ -1029,8 +1033,8 @@ public class ITDSpaceAIP extends AbstractUnitTest { // Change the Item name String newName = "This is NOT my Item name!"; - itemService.clearMetadata(context, testItem, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY); - itemService.addMetadata(context, testItem, MetadataSchema.DC_SCHEMA, "title", null, null, newName); + itemService.clearMetadata(context, testItem, MetadataSchemaEnum.DC.getName(), "title", null, Item.ANY); + itemService.addMetadata(context, testItem, MetadataSchemaEnum.DC.getName(), "title", null, null, newName); // Ensure name is changed assertEquals("testReplaceItem() new name", testItem.getName(), newName); diff --git a/dspace-api/src/test/java/org/dspace/content/packager/PackageUtilsTest.java b/dspace-api/src/test/java/org/dspace/content/packager/PackageUtilsTest.java index c54aacef1a..d6d67a52e2 100644 --- a/dspace-api/src/test/java/org/dspace/content/packager/PackageUtilsTest.java +++ b/dspace-api/src/test/java/org/dspace/content/packager/PackageUtilsTest.java @@ -20,7 +20,7 @@ import org.dspace.AbstractUnitTest; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; import org.dspace.content.Community; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; @@ -87,13 +87,14 @@ public class PackageUtilsTest extends AbstractUnitTest { // Community topCommunity = communityService.create(null, context); communityService - .addMetadata(context, topCommunity, MetadataSchema.DC_SCHEMA, "title", null, null, "Top Community"); + .addMetadata(context, topCommunity, MetadataSchemaEnum.DC.getName(), "title", null, null, + "Top Community"); communityService.update(context, topCommunity); topCommunityHandle = topCommunity.getHandle(); Community child = communityService.createSubcommunity(context, topCommunity); communityService - .addMetadata(context, child, MetadataSchema.DC_SCHEMA, "title", null, null, "Child Community"); + .addMetadata(context, child, MetadataSchemaEnum.DC.getName(), "title", null, null, "Child Community"); communityService.update(context, child); // Create our primary Test Collection diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index 885c045227..0e7f2bd0eb 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -1,10 +1,9 @@ - + 4.0.0 dspace-oai - war + jar DSpace OAI-PMH - DSpace OAI-PMH Web Application and API + DSpace OAI-PMH Extension dspace-parent @@ -17,34 +16,19 @@ ${basedir}/.. 3.2.10 - 2.0.1 + + 5.86.1.RELEASE - - org.apache.maven.plugins - maven-war-plugin - - true - - WEB-INF/lib/*.jar - WEB-INF/lib/*.jar - - true - - - - prepare-package - - - com.mycila license-maven-plugin - src/main/webapp/** + + src/main/resources/** **/*.xsl @@ -107,74 +91,63 @@ org.apache.commons commons-lang3 - - log4j - log4j - - - org.slf4j - slf4j-log4j12 - + + log4j + log4j + + + org.slf4j + slf4j-log4j12 + + + + org.codehaus.woodstox + wstx-asl + - + javax.inject javax.inject 1 + + - org.springframework - spring-webmvc - ${spring.version} - - - org.springframework - spring-beans - ${spring.version} - - - org.springframework - spring-context - ${spring.version} - - - org.springframework - spring-web - ${spring.version} + org.springframework.boot + spring-boot-starter + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-logging + + - com.lyncode - jtwig-spring + org.jtwig + jtwig-spring-boot-starter ${jtwig.version} + - com.google.guava - guava + org.springframework.boot + spring-boot-starter-web + - org.javassist - javassist + org.ow2.asm + asm + - org.apache.commons - commons-lang3 + org.springframework.boot + spring-boot-configuration-processor - - javax.servlet - servlet-api - - - log4j - log4j - - - org.slf4j - slf4j-log4j12 - @@ -284,7 +257,7 @@ org.parboiled parboiled-core - 1.1.6 + 1.1.7 test @@ -294,24 +267,4 @@ test - - - - DSpace @ Lyncode - dspace@lyncode.com - Lyncode - http://www.lyncode.com - - - helix84 - Ivan Másar - helix84@centrum.sk - - - Ariel J. Lira - arieljlira@gmail.com - SeDiCI - http://sedici.unlp.edu.ar - - diff --git a/dspace-oai/src/main/java/org/dspace/app/configuration/OAIWebConfig.java b/dspace-oai/src/main/java/org/dspace/app/configuration/OAIWebConfig.java new file mode 100644 index 0000000000..806ecf9d07 --- /dev/null +++ b/dspace-oai/src/main/java/org/dspace/app/configuration/OAIWebConfig.java @@ -0,0 +1,75 @@ +/** + * 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.configuration; +import static java.lang.Integer.MAX_VALUE; + +import org.dspace.xoai.app.BasicConfiguration; +import org.dspace.xoai.services.api.xoai.ItemRepositoryResolver; +import org.dspace.xoai.services.impl.xoai.DSpaceItemRepositoryResolver; +import org.jtwig.spring.JtwigViewResolver; +import org.jtwig.spring.boot.config.JtwigViewResolverConfigurer; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +/** + * OAI-PMH webapp configuration. Replaces the old web.xml + *

+ * This @Configuration class is automatically discovered by Spring Boot via a @ComponentScan + * on the org.dspace.app.configuration package. + *

+ * + * + * @author Tim Donohue + */ +@Configuration +// Import additional configuration and beans from BasicConfiguration +@Import(BasicConfiguration.class) +// Scan for controllers in this package +@ComponentScan("org.dspace.xoai.controller") +public class OAIWebConfig extends WebMvcConfigurerAdapter implements JtwigViewResolverConfigurer { + + // Path where OAI is deployed. Defaults to "oai" + // NOTE: deployment on this path is handled by org.dspace.xoai.controller.DSpaceOAIDataProvider + @Value("${oai.path:oai}") + private String oaiPath; + + private static final String TWIG_HTML_EXTENSION = ".twig.html"; + private static final String VIEWS_LOCATION = "classpath:/templates/"; + + /** + * Ensure all resources under src/main/resources/static/ directory are available + * off the /{oai.path}/static subpath + **/ + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/" + oaiPath + "/static/**") + .addResourceLocations("classpath:/static/") + .setCachePeriod(MAX_VALUE); + } + + /** + * Configure the Jtwig template engine for Spring Boot + * Ensures Jtwig looks for templates in proper location with proper extension + **/ + @Override + public void configure(JtwigViewResolver viewResolver) { + viewResolver.setPrefix(VIEWS_LOCATION); + viewResolver.setSuffix(TWIG_HTML_EXTENSION); + } + + @Bean + public ItemRepositoryResolver xoaiItemRepositoryResolver() { + return new DSpaceItemRepositoryResolver(); + } +} + diff --git a/dspace-oai/src/main/java/org/dspace/xoai/app/DSpaceWebappConfiguration.java b/dspace-oai/src/main/java/org/dspace/xoai/app/DSpaceWebappConfiguration.java deleted file mode 100644 index 8ad41abb65..0000000000 --- a/dspace-oai/src/main/java/org/dspace/xoai/app/DSpaceWebappConfiguration.java +++ /dev/null @@ -1,62 +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.xoai.app; - -import static java.lang.Integer.MAX_VALUE; - -import com.lyncode.jtwig.mvc.JtwigViewResolver; -import org.dspace.xoai.services.api.xoai.ItemRepositoryResolver; -import org.dspace.xoai.services.impl.xoai.DSpaceItemRepositoryResolver; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.web.servlet.ViewResolver; -import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; - -@Import( { - BasicConfiguration.class -}) -@Configuration -@EnableWebMvc -@ComponentScan("org.dspace.xoai.controller") -public class DSpaceWebappConfiguration extends WebMvcConfigurerAdapter { - private static final String TWIG_HTML_EXTENSION = ".twig.html"; - private static final String VIEWS_LOCATION = "/WEB-INF/views/"; - - @Override - public void addResourceHandlers(ResourceHandlerRegistry registry) { - registry.addResourceHandler("/static/**") - .addResourceLocations("/static/") - .setCachePeriod(MAX_VALUE); - } - - @Override - public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { - configurer.enable(); - } - - @Bean - public ViewResolver viewResolver() { - JtwigViewResolver viewResolver = new JtwigViewResolver(); - viewResolver.setPrefix(VIEWS_LOCATION); - viewResolver.setSuffix(TWIG_HTML_EXTENSION); - viewResolver.setCached(false); - - return viewResolver; - } - - @Bean - public ItemRepositoryResolver xoaiItemRepositoryResolver() { - return new DSpaceItemRepositoryResolver(); - } - -} diff --git a/dspace-oai/src/main/java/org/dspace/xoai/controller/DSpaceOAIDataProvider.java b/dspace-oai/src/main/java/org/dspace/xoai/controller/DSpaceOAIDataProvider.java index 96e82d8eae..212f1e3406 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/controller/DSpaceOAIDataProvider.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/controller/DSpaceOAIDataProvider.java @@ -40,6 +40,7 @@ import org.dspace.xoai.services.api.xoai.ItemRepositoryResolver; import org.dspace.xoai.services.api.xoai.SetRepositoryResolver; import org.dspace.xoai.services.impl.xoai.DSpaceResumptionTokenFormatter; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.PathVariable; @@ -49,6 +50,10 @@ import org.springframework.web.bind.annotation.RequestMapping; * @author Lyncode Development Team (dspace at lyncode dot com) */ @Controller +// Use the configured "oai.path" for all requests, or "/oai" by default +@RequestMapping("/${oai.path:oai}") +// Only enable this controller if "oai.enabled=true" +@ConditionalOnProperty("oai.enabled") public class DSpaceOAIDataProvider { private static final Logger log = getLogger(DSpaceOAIDataProvider.class); @@ -67,7 +72,7 @@ public class DSpaceOAIDataProvider { private DSpaceResumptionTokenFormatter resumptionTokenFormat = new DSpaceResumptionTokenFormatter(); - @RequestMapping("/") + @RequestMapping({"", "/"}) public String indexAction(HttpServletResponse response, Model model) throws ServletException { try { XOAIManager manager = xoaiManagerResolver.getManager(); diff --git a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/DSpaceEarliestDateResolver.java b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/DSpaceEarliestDateResolver.java index de8dc0d4ad..ba51c62d1b 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/DSpaceEarliestDateResolver.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/DSpaceEarliestDateResolver.java @@ -10,6 +10,8 @@ package org.dspace.xoai.services.impl; import java.sql.SQLException; import java.util.Date; +import javax.persistence.NoResultException; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.content.MetadataValue; @@ -30,11 +32,16 @@ public class DSpaceEarliestDateResolver implements EarliestDateResolver { @Override public Date getEarliestDate(Context context) throws InvalidMetadataFieldException, SQLException { - String query = "SELECT MIN(text_value) as value FROM metadatavalue WHERE metadata_field_id = ?"; - MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService(); - MetadataValue minimum = metadataValueService.getMinimum(context, - fieldResolver.getFieldID(context, "dc.date.available")); + MetadataValue minimum = null; + try { + minimum = metadataValueService.getMinimum(context, + fieldResolver.getFieldID(context, "dc.date.available")); + } catch (NoResultException e) { + // This error only occurs if no metadataFields of this type exist (i.e. no minimum exists) + // It can be safely ignored in this scenario, as it implies the DSpace is empty. + } + if (null != minimum) { String str = minimum.getValue(); try { diff --git a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/xoai/DSpaceRepositoryConfiguration.java b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/xoai/DSpaceRepositoryConfiguration.java index 979520c199..fe7fbdbfd0 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/xoai/DSpaceRepositoryConfiguration.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/xoai/DSpaceRepositoryConfiguration.java @@ -27,6 +27,8 @@ import org.dspace.xoai.services.api.EarliestDateResolver; import org.dspace.xoai.services.api.config.ConfigurationService; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import org.springframework.web.util.UriComponentsBuilder; /** * @author Lyncode Development Team (dspace at lyncode dot com) @@ -68,17 +70,27 @@ public class DSpaceRepositoryConfiguration implements RepositoryConfiguration { public String getBaseUrl() { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()) .getRequest(); + + // Parse the current OAI "context" path out of the last HTTP request. + // (e.g. for "http://mydspace.edu/oai/request", the oaiContextPath is "request") + UriComponentsBuilder builder = ServletUriComponentsBuilder.fromRequest(request); + List pathSegments = builder.buildAndExpand().getPathSegments(); + String oaiContextPath = pathSegments.get(pathSegments.size() - 1); + if (baseUrl == null) { baseUrl = configurationService.getProperty("oai.url"); if (baseUrl == null) { log.warn( "{ OAI 2.0 :: DSpace } Not able to retrieve the oai.url property from oai.cfg. Falling back to " + "request address"); + // initialize baseUrl to a fallback "oai.url" which is the current request with OAI context removed. baseUrl = request.getRequestURL().toString() - .replace(request.getPathInfo(), ""); + .replace(oaiContextPath, ""); } } - return baseUrl + request.getPathInfo(); + + // BaseURL is the path of OAI with the current OAI context appended + return baseUrl + "/" + oaiContextPath; } @Override diff --git a/dspace-oai/src/main/webapp/static/css/bootstrap-theme.css b/dspace-oai/src/main/resources/static/css/bootstrap-theme.css similarity index 100% rename from dspace-oai/src/main/webapp/static/css/bootstrap-theme.css rename to dspace-oai/src/main/resources/static/css/bootstrap-theme.css diff --git a/dspace-oai/src/main/webapp/static/css/bootstrap-theme.min.css b/dspace-oai/src/main/resources/static/css/bootstrap-theme.min.css similarity index 100% rename from dspace-oai/src/main/webapp/static/css/bootstrap-theme.min.css rename to dspace-oai/src/main/resources/static/css/bootstrap-theme.min.css diff --git a/dspace-oai/src/main/webapp/static/css/bootstrap.css b/dspace-oai/src/main/resources/static/css/bootstrap.css similarity index 100% rename from dspace-oai/src/main/webapp/static/css/bootstrap.css rename to dspace-oai/src/main/resources/static/css/bootstrap.css diff --git a/dspace-oai/src/main/webapp/static/css/bootstrap.min.css b/dspace-oai/src/main/resources/static/css/bootstrap.min.css similarity index 100% rename from dspace-oai/src/main/webapp/static/css/bootstrap.min.css rename to dspace-oai/src/main/resources/static/css/bootstrap.min.css diff --git a/dspace-oai/src/main/webapp/static/css/style.css b/dspace-oai/src/main/resources/static/css/style.css similarity index 100% rename from dspace-oai/src/main/webapp/static/css/style.css rename to dspace-oai/src/main/resources/static/css/style.css diff --git a/dspace-oai/src/main/webapp/static/fonts/glyphicons-halflings-regular.eot b/dspace-oai/src/main/resources/static/fonts/glyphicons-halflings-regular.eot similarity index 100% rename from dspace-oai/src/main/webapp/static/fonts/glyphicons-halflings-regular.eot rename to dspace-oai/src/main/resources/static/fonts/glyphicons-halflings-regular.eot diff --git a/dspace-oai/src/main/webapp/static/fonts/glyphicons-halflings-regular.svg b/dspace-oai/src/main/resources/static/fonts/glyphicons-halflings-regular.svg similarity index 100% rename from dspace-oai/src/main/webapp/static/fonts/glyphicons-halflings-regular.svg rename to dspace-oai/src/main/resources/static/fonts/glyphicons-halflings-regular.svg diff --git a/dspace-oai/src/main/webapp/static/fonts/glyphicons-halflings-regular.ttf b/dspace-oai/src/main/resources/static/fonts/glyphicons-halflings-regular.ttf similarity index 100% rename from dspace-oai/src/main/webapp/static/fonts/glyphicons-halflings-regular.ttf rename to dspace-oai/src/main/resources/static/fonts/glyphicons-halflings-regular.ttf diff --git a/dspace-oai/src/main/webapp/static/fonts/glyphicons-halflings-regular.woff b/dspace-oai/src/main/resources/static/fonts/glyphicons-halflings-regular.woff similarity index 100% rename from dspace-oai/src/main/webapp/static/fonts/glyphicons-halflings-regular.woff rename to dspace-oai/src/main/resources/static/fonts/glyphicons-halflings-regular.woff diff --git a/dspace-oai/src/main/webapp/static/img/lyncode.png b/dspace-oai/src/main/resources/static/img/lyncode.png similarity index 100% rename from dspace-oai/src/main/webapp/static/img/lyncode.png rename to dspace-oai/src/main/resources/static/img/lyncode.png diff --git a/dspace-oai/src/main/webapp/static/js/bootstrap.js b/dspace-oai/src/main/resources/static/js/bootstrap.js similarity index 100% rename from dspace-oai/src/main/webapp/static/js/bootstrap.js rename to dspace-oai/src/main/resources/static/js/bootstrap.js diff --git a/dspace-oai/src/main/webapp/static/js/bootstrap.min.js b/dspace-oai/src/main/resources/static/js/bootstrap.min.js similarity index 100% rename from dspace-oai/src/main/webapp/static/js/bootstrap.min.js rename to dspace-oai/src/main/resources/static/js/bootstrap.min.js diff --git a/dspace-oai/src/main/webapp/static/js/jquery.js b/dspace-oai/src/main/resources/static/js/jquery.js similarity index 100% rename from dspace-oai/src/main/webapp/static/js/jquery.js rename to dspace-oai/src/main/resources/static/js/jquery.js diff --git a/dspace-oai/src/main/webapp/static/lyncode.png b/dspace-oai/src/main/resources/static/lyncode.png similarity index 100% rename from dspace-oai/src/main/webapp/static/lyncode.png rename to dspace-oai/src/main/resources/static/lyncode.png diff --git a/dspace-oai/src/main/webapp/static/style.xsl b/dspace-oai/src/main/resources/static/style.xsl similarity index 100% rename from dspace-oai/src/main/webapp/static/style.xsl rename to dspace-oai/src/main/resources/static/style.xsl diff --git a/dspace-oai/src/main/webapp/WEB-INF/views/index.twig.html b/dspace-oai/src/main/resources/templates/index.twig.html similarity index 74% rename from dspace-oai/src/main/webapp/WEB-INF/views/index.twig.html rename to dspace-oai/src/main/resources/templates/index.twig.html index fc530902a3..0045d8f5e5 100644 --- a/dspace-oai/src/main/webapp/WEB-INF/views/index.twig.html +++ b/dspace-oai/src/main/resources/templates/index.twig.html @@ -6,6 +6,12 @@ http://www.dspace.org/license/ +#} +{# + + DSpace OAI default index template. To override this template, place a customized version in + the [webapp]/WEB-INF/classes/templates/ folder, and reboot your servlet engine. + #} @@ -13,8 +19,8 @@ DSpace OAI-PMH Data Provider - - + + @@ -24,9 +30,9 @@ - - - + + + @@ -56,11 +62,11 @@

@@ -73,11 +79,11 @@

Design by Lyncode

- Lyncode + Lyncode

- \ No newline at end of file + diff --git a/dspace-oai/src/main/webapp/WEB-INF/spring/applicationContext.xml b/dspace-oai/src/main/webapp/WEB-INF/spring/applicationContext.xml deleted file mode 100644 index 0b6f2cde61..0000000000 --- a/dspace-oai/src/main/webapp/WEB-INF/spring/applicationContext.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/dspace-oai/src/main/webapp/WEB-INF/web.xml b/dspace-oai/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index d731a0c659..0000000000 --- a/dspace-oai/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - XOAI Data Provider - - - The location of the DSpace home directory - dspace.dir - ${dspace.dir} - - - - log4jConfiguration - ${dspace.dir}/config/log4j2.xml - The location of the Log4J configuration - - - - - - contextConfigLocation - /WEB-INF/spring/*.xml - - - - - org.dspace.app.util.DSpaceContextListener - - - - - - org.dspace.servicemanager.servlet.DSpaceKernelServletContextListener - - - - - - org.dspace.app.util.DSpaceWebappListener - - - - - org.springframework.web.context.ContextLoaderListener - - - - oai - org.springframework.web.servlet.DispatcherServlet - - - contextClass - - org.springframework.web.context.support.AnnotationConfigWebApplicationContext - - - - - - contextConfigLocation - org.dspace.xoai.app.DSpaceWebappConfiguration - - - - - - oai - /* - - - diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/DSpaceBasicTestConfiguration.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/DSpaceBasicTestConfiguration.java index e50f2f969d..7b81468a8e 100644 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/DSpaceBasicTestConfiguration.java +++ b/dspace-oai/src/test/java/org/dspace/xoai/tests/DSpaceBasicTestConfiguration.java @@ -10,11 +10,9 @@ package org.dspace.xoai.tests; import static org.mockito.Mockito.mock; import org.dspace.xoai.services.api.FieldResolver; -import org.dspace.xoai.services.api.config.ConfigurationService; import org.dspace.xoai.services.api.context.ContextService; import org.dspace.xoai.services.api.xoai.DSpaceFilterResolver; import org.dspace.xoai.services.impl.xoai.BaseDSpaceFilterResolver; -import org.dspace.xoai.tests.helpers.stubs.StubbedConfigurationService; import org.dspace.xoai.tests.helpers.stubs.StubbedFieldResolver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -29,14 +27,6 @@ public class DSpaceBasicTestConfiguration { return new BaseDSpaceFilterResolver(); } - - private StubbedConfigurationService configurationService = new StubbedConfigurationService(); - - @Bean - public ConfigurationService configurationService() { - return configurationService; - } - @Bean public ContextService contextService() { return mock(ContextService.class); diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/DSpaceTestConfiguration.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/DSpaceTestConfiguration.java deleted file mode 100644 index bc526cdf53..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/DSpaceTestConfiguration.java +++ /dev/null @@ -1,123 +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.xoai.tests; - -import com.lyncode.xoai.dataprovider.services.api.ItemRepository; -import com.lyncode.xoai.dataprovider.services.api.ResourceResolver; -import com.lyncode.xoai.dataprovider.services.api.SetRepository; -import org.dspace.core.Context; -import org.dspace.xoai.services.api.EarliestDateResolver; -import org.dspace.xoai.services.api.cache.XOAICacheService; -import org.dspace.xoai.services.api.config.XOAIManagerResolver; -import org.dspace.xoai.services.api.context.ContextService; -import org.dspace.xoai.services.api.context.ContextServiceException; -import org.dspace.xoai.services.api.xoai.IdentifyResolver; -import org.dspace.xoai.services.api.xoai.ItemRepositoryResolver; -import org.dspace.xoai.services.api.xoai.SetRepositoryResolver; -import org.dspace.xoai.services.impl.cache.DSpaceEmptyCacheService; -import org.dspace.xoai.services.impl.xoai.DSpaceIdentifyResolver; -import org.dspace.xoai.tests.helpers.stubs.StubbedEarliestDateResolver; -import org.dspace.xoai.tests.helpers.stubs.StubbedResourceResolver; -import org.dspace.xoai.tests.helpers.stubs.StubbedSetRepository; -import org.dspace.xoai.tests.helpers.stubs.StubbedXOAIManagerResolver; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.web.servlet.ViewResolver; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; -import org.springframework.web.servlet.view.InternalResourceViewResolver; - -@Import(DSpaceBasicTestConfiguration.class) -@Configuration -@EnableWebMvc -public class DSpaceTestConfiguration extends WebMvcConfigurerAdapter { - private static final String TWIG_HTML_EXTENSION = ".twig.html"; - private static final String VIEWS_LOCATION = "/WEB-INF/views/"; - - - @Bean - public ContextService contextService() { - return new ContextService() { - @Override - public Context getContext() throws ContextServiceException { - return null; - } - }; - } - - private StubbedResourceResolver resourceResolver = new StubbedResourceResolver(); - - @Bean - public ResourceResolver resourceResolver() { - return resourceResolver; - } - - @Bean - public ViewResolver viewResolver() { - InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); - viewResolver.setPrefix(VIEWS_LOCATION); - viewResolver.setSuffix(TWIG_HTML_EXTENSION); -// viewResolver.setCached(true); -// viewResolver.setTheme(null); - - return viewResolver; - } - - - @Bean - public XOAIManagerResolver xoaiManagerResolver() { - return new StubbedXOAIManagerResolver(); - } - - @Bean - public XOAICacheService xoaiCacheService() { - return new DSpaceEmptyCacheService(); - } - - private StubbedSetRepository setRepository = new StubbedSetRepository(); - - @Bean - StubbedSetRepository setRepository() { - return setRepository; - } - - @Bean - public ItemRepositoryResolver itemRepositoryResolver() { - return new ItemRepositoryResolver() { - @Override - public ItemRepository getItemRepository() throws ContextServiceException { - try { - return null; - } catch (Exception e) { - throw new ContextServiceException(e); - } - } - }; - } - - @Bean - public SetRepositoryResolver setRepositoryResolver() { - return new SetRepositoryResolver() { - @Override - public SetRepository getSetRepository() throws ContextServiceException { - return setRepository; - } - }; - } - - @Bean - public IdentifyResolver identifyResolver() { - return new DSpaceIdentifyResolver(); - } - - @Bean - public EarliestDateResolver earliestDateResolver() { - return new StubbedEarliestDateResolver(); - } -} diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/HttpRequestBuilder.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/HttpRequestBuilder.java deleted file mode 100644 index 153b05576e..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/HttpRequestBuilder.java +++ /dev/null @@ -1,37 +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.xoai.tests.helpers; - -import java.net.URI; -import java.net.URISyntaxException; -import javax.servlet.http.HttpServletRequest; - -import org.springframework.mock.web.MockHttpServletRequest; - -public class HttpRequestBuilder { - private MockHttpServletRequest request = new MockHttpServletRequest(); - - public HttpRequestBuilder withUrl(String url) { - try { - URI uri = new URI(url); - } catch (URISyntaxException e) { - // ASD - } - return this; - } - - public HttpRequestBuilder usingGetMethod() { - request.setMethod("GET"); - return this; - } - - public HttpServletRequest build() { - - return request; - } -} diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/SyntacticSugar.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/SyntacticSugar.java deleted file mode 100644 index a6cd3a3249..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/SyntacticSugar.java +++ /dev/null @@ -1,28 +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.xoai.tests.helpers; - -public class SyntacticSugar { - - /** - * Default constructor - */ - private SyntacticSugar() { } - - public static T given(T elem) { - return elem; - } - - public static T the(T elem) { - return elem; - } - - public static T and(T elem) { - return elem; - } -} diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/ItemRepositoryBuilder.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/ItemRepositoryBuilder.java deleted file mode 100644 index c01580e990..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/ItemRepositoryBuilder.java +++ /dev/null @@ -1,183 +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.xoai.tests.helpers.stubs; - -import static com.lyncode.xoai.dataprovider.core.Granularity.Second; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.sql.SQLException; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import javax.xml.stream.XMLStreamException; - -import com.lyncode.xoai.builders.dataprovider.ElementBuilder; -import com.lyncode.xoai.builders.dataprovider.MetadataBuilder; -import com.lyncode.xoai.dataprovider.exceptions.MetadataBindException; -import com.lyncode.xoai.dataprovider.exceptions.WritingXmlException; -import com.lyncode.xoai.dataprovider.xml.XmlOutputContext; -import com.lyncode.xoai.dataprovider.xml.xoai.Metadata; -import org.apache.solr.client.solrj.SolrClient; -import org.apache.solr.client.solrj.SolrServerException; -import org.apache.solr.common.SolrInputDocument; - -public class ItemRepositoryBuilder { - private final SolrClient solrServer; - - public ItemRepositoryBuilder(SolrClient solrServer) { - this.solrServer = solrServer; - } - - public ItemRepositoryBuilder withItem(DSpaceItemBuilder builder) { - try { - solrServer.add(index(builder)); - solrServer.commit(); - } catch (MetadataBindException | WritingXmlException | IOException - | SQLException | ParseException | XMLStreamException - | SolrServerException e) { - throw new RuntimeException(e); - } - return this; - } - - - private SolrInputDocument index(DSpaceItemBuilder item) - throws SQLException, MetadataBindException, ParseException, XMLStreamException, WritingXmlException { - SolrInputDocument doc = new SolrInputDocument(); - - doc.addField("item.id", item.getId()); - doc.addField("item.public", item.isPublic()); - doc.addField("item.lastmodified", item.getLastModifiedDate()); - doc.addField("item.submitter", item.getSubmitter()); - doc.addField("item.handle", item.getHandle()); - doc.addField("item.deleted", item.isDeleted()); - - for (String col : item.getCollections()) { - doc.addField("item.collections", col); - } - - for (String col : item.getCommunities()) { - doc.addField("item.communities", col); - } - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - XmlOutputContext context = XmlOutputContext.emptyContext(out, Second); - item.getMetadata().write(context); - context.getWriter().flush(); - context.getWriter().close(); - doc.addField("item.compile", out.toString()); - - return doc; - } - - public static class DSpaceItemBuilder { - private final List collections = new ArrayList<>(); - private final List communities = new ArrayList<>(); - private final MetadataBuilder metadataBuilder = new MetadataBuilder(); - private String handle; - private int id; - private String submitter; - private Date lastModifiedDate; - private boolean deleted; - private boolean aPublic = true; - - - public DSpaceItemBuilder withLastModifiedDate(Date lastModifiedDate) { - this.lastModifiedDate = lastModifiedDate; - return this; - } - - public DSpaceItemBuilder withCollection(String colName) { - collections.add(colName); - return this; - } - - public DSpaceItemBuilder withCommunity(String comName) { - communities.add(comName); - return this; - } - - public DSpaceItemBuilder whichSsPublic() { - aPublic = true; - return this; - } - - public DSpaceItemBuilder whichSsPrivate() { - aPublic = false; - return this; - } - - public DSpaceItemBuilder whichIsDeleted() { - this.deleted = true; - return this; - } - - public DSpaceItemBuilder whichIsNotDeleted() { - this.deleted = false; - return this; - } - - public DSpaceItemBuilder withMetadata(String schema, String element, String value) { - metadataBuilder.withElement(new ElementBuilder().withName(schema).withField(element, value).build()); - return this; - } - - public String getHandle() { - return handle; - } - - public DSpaceItemBuilder withHandle(String handle) { - this.handle = handle; - return this; - } - - public DSpaceItemBuilder withSubmitter(String submitter) { - this.submitter = submitter; - return this; - } - - public DSpaceItemBuilder withId(int id) { - this.id = id; - return this; - } - - public int getId() { - return id; - } - - public String getSubmitter() { - return submitter; - } - - public Date getLastModifiedDate() { - return lastModifiedDate; - } - - public List getCollections() { - return collections; - } - - public List getCommunities() { - return communities; - } - - public Metadata getMetadata() { - return metadataBuilder.build(); - } - - public boolean isDeleted() { - return deleted; - } - - public boolean isPublic() { - return aPublic; - } - } -} diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedConfigurationService.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedConfigurationService.java deleted file mode 100644 index c6e7a3cc99..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedConfigurationService.java +++ /dev/null @@ -1,63 +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.xoai.tests.helpers.stubs; - -import java.util.HashMap; -import java.util.Map; - -import org.dspace.xoai.services.api.config.ConfigurationService; - -public class StubbedConfigurationService implements ConfigurationService { - private Map values = new HashMap(); - - - public StubbedConfigurationService hasBooleanProperty(String key, boolean value) { - values.put(key, value); - return this; - } - - public StubbedConfigurationService hasBooleanProperty(String module, String key, boolean value) { - values.put(module + "." + key, value); - return this; - } - - public StubbedConfigurationService hasProperty(String key, String value) { - values.put(key, value); - return this; - } - - public StubbedConfigurationService withoutProperty(String key) { - values.remove(key); - return this; - } - - public StubbedConfigurationService hasProperty(String module, String key, String value) { - values.put(module + "." + key, value); - return this; - } - - @Override - public String getProperty(String key) { - return (String) values.get(key); - } - - @Override - public String getProperty(String module, String key) { - return (String) values.get(module + "." + key); - } - - @Override - public boolean getBooleanProperty(String module, String key, boolean defaultValue) { - Boolean value = (Boolean) values.get(module + "." + key); - if (value == null) { - return defaultValue; - } else { - return value; - } - } -} diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedEarliestDateResolver.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedEarliestDateResolver.java deleted file mode 100644 index a585e1427e..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedEarliestDateResolver.java +++ /dev/null @@ -1,29 +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.xoai.tests.helpers.stubs; - -import java.sql.SQLException; -import java.util.Date; - -import org.dspace.core.Context; -import org.dspace.xoai.exceptions.InvalidMetadataFieldException; -import org.dspace.xoai.services.api.EarliestDateResolver; - -public class StubbedEarliestDateResolver implements EarliestDateResolver { - private Date date = new Date(); - - public StubbedEarliestDateResolver is(Date date) { - this.date = date; - return this; - } - - @Override - public Date getEarliestDate(Context context) throws InvalidMetadataFieldException, SQLException { - return date; - } -} diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedResourceResolver.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedResourceResolver.java deleted file mode 100644 index 464f8fc487..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedResourceResolver.java +++ /dev/null @@ -1,44 +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.xoai.tests.helpers.stubs; - -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerConfigurationException; -import javax.xml.transform.TransformerFactory; - -import com.lyncode.xoai.dataprovider.services.api.ResourceResolver; - -public class StubbedResourceResolver implements ResourceResolver { - private static TransformerFactory factory = TransformerFactory.newInstance(); - - private Map inputStreamMap = new HashMap(); - private Map transformerMap = new HashMap(); - - @Override - public InputStream getResource(String path) throws IOException { - return inputStreamMap.get(path); - } - - @Override - public Transformer getTransformer(String path) throws IOException, TransformerConfigurationException { - return transformerMap.get(path); - } - - public StubbedResourceResolver hasIdentityTransformerFor(String path) { - try { - transformerMap.put(path, factory.newTransformer()); - } catch (TransformerConfigurationException e) { - throw new RuntimeException(e); - } - return this; - } -} diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedSetRepository.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedSetRepository.java deleted file mode 100644 index 8432633e7b..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedSetRepository.java +++ /dev/null @@ -1,74 +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.xoai.tests.helpers.stubs; - -import static java.lang.Math.min; -import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic; - -import java.util.ArrayList; -import java.util.List; - -import com.lyncode.xoai.dataprovider.core.ListSetsResult; -import com.lyncode.xoai.dataprovider.core.Set; -import com.lyncode.xoai.dataprovider.services.api.SetRepository; - -public class StubbedSetRepository implements SetRepository { - private List sets = new ArrayList(); - private boolean supports = false; - - @Override - public boolean supportSets() { - return supports; - } - - @Override - public ListSetsResult retrieveSets(int offset, int length) { - if (offset > sets.size()) { - return new ListSetsResult(false, new ArrayList(), sets.size()); - } - return new ListSetsResult(offset + length < sets.size(), - sets.subList(offset, min(offset + length, sets.size())), sets.size()); - } - - @Override - public boolean exists(String setSpec) { - for (Set set : sets) { - if (set.getSetSpec().equals(setSpec)) { - return true; - } - } - - return false; - } - - public StubbedSetRepository doesSupportSets() { - this.supports = true; - return this; - } - - public StubbedSetRepository doesNotSupportSets() { - this.supports = false; - return this; - } - - public StubbedSetRepository withSet(String name, String spec) { - this.sets.add(new Set(spec, name)); - return this; - } - - public StubbedSetRepository withRandomlyGeneratedSets(int number) { - for (int i = 0; i < number; i++) { - this.sets.add(new Set(randomAlphabetic(10), randomAlphabetic(10))); - } - return this; - } - - public void clear() { - this.sets.clear(); - } -} diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedXOAIManagerResolver.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedXOAIManagerResolver.java deleted file mode 100644 index f0d3275f74..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/helpers/stubs/StubbedXOAIManagerResolver.java +++ /dev/null @@ -1,39 +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.xoai.tests.helpers.stubs; - -import com.lyncode.xoai.dataprovider.core.XOAIManager; -import com.lyncode.xoai.dataprovider.exceptions.ConfigurationException; -import com.lyncode.xoai.dataprovider.services.api.ResourceResolver; -import com.lyncode.xoai.dataprovider.xml.xoaiconfig.Configuration; -import org.dspace.xoai.services.api.config.XOAIManagerResolver; -import org.dspace.xoai.services.api.config.XOAIManagerResolverException; -import org.dspace.xoai.services.api.xoai.DSpaceFilterResolver; -import org.springframework.beans.factory.annotation.Autowired; - -public class StubbedXOAIManagerResolver implements XOAIManagerResolver { - @Autowired - ResourceResolver resourceResolver; - @Autowired - DSpaceFilterResolver filterResolver; - - private Configuration builder = new Configuration(); - - public Configuration configuration() { - return builder; - } - - @Override - public XOAIManager getManager() throws XOAIManagerResolverException { - try { - return new XOAIManager(filterResolver, resourceResolver, builder); - } catch (ConfigurationException e) { - throw new XOAIManagerResolverException(e); - } - } -} diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/AbstractDSpaceTest.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/AbstractDSpaceTest.java deleted file mode 100644 index d9824d8c53..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/AbstractDSpaceTest.java +++ /dev/null @@ -1,186 +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.xoai.tests.integration.xoai; - -import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; - -import java.util.Date; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.xml.xpath.XPathExpressionException; - -import com.lyncode.builder.MapBuilder; -import com.lyncode.xoai.dataprovider.services.api.ResourceResolver; -import com.lyncode.xoai.dataprovider.services.impl.BaseDateProvider; -import com.lyncode.xoai.dataprovider.xml.xoaiconfig.Configuration; -import com.lyncode.xoai.dataprovider.xml.xoaiconfig.FormatConfiguration; -import org.apache.solr.client.solrj.SolrClient; -import org.dspace.xoai.controller.DSpaceOAIDataProvider; -import org.dspace.xoai.services.api.EarliestDateResolver; -import org.dspace.xoai.services.api.FieldResolver; -import org.dspace.xoai.services.api.config.ConfigurationService; -import org.dspace.xoai.services.api.config.XOAIManagerResolver; -import org.dspace.xoai.tests.DSpaceTestConfiguration; -import org.dspace.xoai.tests.helpers.stubs.ItemRepositoryBuilder; -import org.dspace.xoai.tests.helpers.stubs.StubbedConfigurationService; -import org.dspace.xoai.tests.helpers.stubs.StubbedEarliestDateResolver; -import org.dspace.xoai.tests.helpers.stubs.StubbedFieldResolver; -import org.dspace.xoai.tests.helpers.stubs.StubbedResourceResolver; -import org.dspace.xoai.tests.helpers.stubs.StubbedSetRepository; -import org.dspace.xoai.tests.helpers.stubs.StubbedXOAIManagerResolver; -import org.junit.After; -import org.junit.Before; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.ResultMatcher; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers; -import org.springframework.test.web.servlet.result.XpathResultMatchers; -import org.springframework.web.context.WebApplicationContext; - - -@RunWith(SpringJUnit4ClassRunner.class) -@WebAppConfiguration -@ContextConfiguration(classes = {DSpaceTestConfiguration.class, DSpaceOAIDataProvider.class}) -public abstract class AbstractDSpaceTest { - private static final BaseDateProvider baseDateProvider = new BaseDateProvider(); - @Autowired - WebApplicationContext wac; - private MockMvc mockMvc; - - private StubbedXOAIManagerResolver xoaiManagerResolver; - private StubbedConfigurationService configurationService; - private StubbedFieldResolver databaseService; - private StubbedEarliestDateResolver earliestDateResolver; - private StubbedSetRepository setRepository; - private StubbedResourceResolver resourceResolver; - private SolrClient solrServer; - - @Before - public void setup() { - xoaiManagerResolver = (StubbedXOAIManagerResolver) this.wac.getBean(XOAIManagerResolver.class); - configurationService = (StubbedConfigurationService) this.wac.getBean(ConfigurationService.class); - databaseService = (StubbedFieldResolver) this.wac.getBean(FieldResolver.class); - earliestDateResolver = (StubbedEarliestDateResolver) this.wac.getBean(EarliestDateResolver.class); - setRepository = this.wac.getBean(StubbedSetRepository.class); - setRepository.clear(); -// solrServer = this.wac.getBean(SolrServer.class); - resourceResolver = (StubbedResourceResolver) this.wac.getBean(ResourceResolver.class); - xoaiManagerResolver.configuration(); - } - - @After - public void teardown() { - // Nullify all resources so that JUnit will clean them up - xoaiManagerResolver = null; - configurationService = null; - databaseService = null; - earliestDateResolver = null; - setRepository = null; - resourceResolver = null; - mockMvc = null; - solrServer = null; - } - - protected MockMvc againstTheDataProvider() { - if (this.mockMvc == null) { - this.mockMvc = webAppContextSetup(this.wac).build(); - } - return this.mockMvc; - } - - protected Configuration theConfiguration() { - return xoaiManagerResolver.configuration(); - } - - protected StubbedConfigurationService theDSpaceConfiguration() { - return configurationService; - } - - protected StubbedFieldResolver theDatabase() { - return databaseService; - } - - protected StubbedEarliestDateResolver theEarlistEarliestDate() { - return earliestDateResolver; - } - - protected XpathResultMatchers oaiXPath(String xpath) throws XPathExpressionException { - return MockMvcResultMatchers.xpath(this.replaceXpath(xpath), new MapBuilder() - .withPair("o", "http://www.openarchives.org/OAI/2.0/") - .build()); - } - - private String replaceXpath(String xpath) { - int offset = 0; - String newXpath = ""; - Pattern pattern = Pattern.compile("/[^/]+"); - Matcher matcher = pattern.matcher(xpath); - while (matcher.find()) { - if (matcher.start() > offset) { - newXpath += xpath.substring(offset, matcher.start()); - } - if (!matcher.group().contains(":") && !matcher.group().startsWith("/@")) { - newXpath += "/o:" + matcher.group().substring(1); - } else { - newXpath += matcher.group(); - } - offset = matcher.end() + 1; - } - - return newXpath; - } - - protected String representationOfDate(Date date) { - return baseDateProvider.format(date); - } - - protected StubbedSetRepository theSetRepository() { - return setRepository; - } - - protected com.lyncode.xoai.dataprovider.xml.xoaiconfig.ContextConfiguration aContext(String baseUrl) { - return new com.lyncode.xoai.dataprovider.xml.xoaiconfig.ContextConfiguration(baseUrl); - } - - protected XpathResultMatchers responseDate() throws XPathExpressionException { - return oaiXPath("/OAI-PMH/responseDate"); - } - - protected ResultMatcher verb(org.hamcrest.Matcher matcher) throws XPathExpressionException { - return oaiXPath("/OAI-PMH/request/@verb").string(matcher); - } - - protected XpathResultMatchers resumptionToken() throws XPathExpressionException { - return oaiXPath("//resumptionToken"); - } - - protected StubbedResourceResolver theResourseResolver() { - return resourceResolver; - } - - protected FormatConfiguration aFormat(String id) { - return new FormatConfiguration(id); - } - - protected ItemRepositoryBuilder.DSpaceItemBuilder anItem() { - return new ItemRepositoryBuilder.DSpaceItemBuilder(); - } - - private ItemRepositoryBuilder itemRepositoryBuilder; - - public ItemRepositoryBuilder theItemRepository() { - if (itemRepositoryBuilder == null) { - itemRepositoryBuilder = new ItemRepositoryBuilder(solrServer); - } - return itemRepositoryBuilder; - } -} \ No newline at end of file diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/IdentifyTest.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/IdentifyTest.java deleted file mode 100644 index f49e4e07e2..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/IdentifyTest.java +++ /dev/null @@ -1,50 +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.xoai.tests.integration.xoai; - -import static org.dspace.xoai.tests.helpers.SyntacticSugar.and; -import static org.dspace.xoai.tests.helpers.SyntacticSugar.given; -import static org.hamcrest.core.Is.is; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import java.util.Date; - -import org.junit.Test; - -public class IdentifyTest extends AbstractDSpaceTest { - - public static final Date EARLIEST_DATE = new Date(); - - @Test - public void requestForIdentifyWithoutRequiredConfigurationAdminEmailSetShouldFail() throws Exception { - given(theDSpaceConfiguration() - .withoutProperty("mail.admin")); - and(given(theConfiguration().withContextConfigurations(aContext("request")))); - - againstTheDataProvider().perform(get("/request?verb=Identify")) - .andExpect(status().isInternalServerError()); - } - - @Test - public void requestForIdentifyShouldReturnTheConfiguredValues() throws Exception { - given(theDSpaceConfiguration() - .hasProperty("dspace.name", "Test") - .hasProperty("mail.admin", "test@test.com")); - - and(given(theEarlistEarliestDate().is(EARLIEST_DATE))); - and(given(theConfiguration().withContextConfigurations(aContext("request")))); - - againstTheDataProvider().perform(get("/request?verb=Identify")) - .andExpect(status().isOk()) - .andExpect(oaiXPath("//repositoryName").string("Test")) - .andExpect(oaiXPath("//adminEmail").string("test@test.com")) - .andExpect( - oaiXPath("//earliestDatestamp").string(is(representationOfDate(EARLIEST_DATE)))); - } -} diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/ListSetsTest.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/ListSetsTest.java deleted file mode 100644 index 282d6a6c9d..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/ListSetsTest.java +++ /dev/null @@ -1,65 +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.xoai.tests.integration.xoai; - -import static org.dspace.xoai.tests.helpers.SyntacticSugar.and; -import static org.dspace.xoai.tests.helpers.SyntacticSugar.given; -import static org.hamcrest.core.Is.is; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import org.junit.Test; - -public class ListSetsTest extends AbstractDSpaceTest { - @Test - public void listSetsWithLessSetsThenMaxSetsPerPage() throws Exception { - given(theConfiguration() - .withMaxListSetsSize(100) - .withContextConfigurations(aContext("request"))); - and(given(theSetRepository() - .doesSupportSets() - .withSet("name", "spec"))); - - againstTheDataProvider().perform(get("/request?verb=ListSets")) - .andExpect(status().isOk()) - .andDo(print()) - .andExpect(responseDate().exists()) - .andExpect(verb(is("ListSets"))) - .andExpect(oaiXPath("//set").nodeCount(1)) - .andExpect(oaiXPath("//set/setSpec").string("spec")) - .andExpect(oaiXPath("//set/setName").string("name")) - .andExpect(resumptionToken().doesNotExist()); - } - - @Test - public void listSetsWithMoreSetsThenMaxSetsPerPage() throws Exception { - given(theConfiguration() - .withMaxListSetsSize(10) - .withContextConfigurations(aContext("request"))); - - and(given(theSetRepository() - .doesSupportSets() - .withRandomlyGeneratedSets(20))); - - againstTheDataProvider().perform(get("/request?verb=ListSets")) - .andExpect(status().isOk()) - .andExpect(responseDate().exists()) - .andExpect(verb(is("ListSets"))) - .andExpect(oaiXPath("//set").nodeCount(10)) - .andExpect(resumptionToken().string("////10")) - .andExpect(oaiXPath("//resumptionToken/@completeListSize").number(Double.valueOf(20))); - - and(againstTheDataProvider().perform(get("/request?verb=ListSets&resumptionToken=////10")) - .andExpect(status().isOk()) - .andExpect(responseDate().exists()) - .andExpect(verb(is("ListSets"))) - .andExpect(oaiXPath("//set").nodeCount(10)) - .andExpect(resumptionToken().string(""))); - } -} diff --git a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/OAIContextTest.java b/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/OAIContextTest.java deleted file mode 100644 index ef875e1896..0000000000 --- a/dspace-oai/src/test/java/org/dspace/xoai/tests/integration/xoai/OAIContextTest.java +++ /dev/null @@ -1,35 +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.xoai.tests.integration.xoai; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import org.junit.Test; - -public class OAIContextTest extends AbstractDSpaceTest { - public static final String ROOT_URL = "/"; - - @Test - public void requestToRootShouldGiveListOfContextsWithBadRequestError() throws Exception { - againstTheDataProvider().perform(get(ROOT_URL)) - .andDo(print()) - .andExpect(status().isBadRequest()) - .andExpect(model().attributeExists("contexts")); - } - - @Test - public void requestForUnknownContextShouldGiveListOfContextsWithBadRequestError() throws Exception { - againstTheDataProvider().perform(get("/unexistentContext")) - .andDo(print()) - .andExpect(status().isBadRequest()) - .andExpect(model().attributeExists("contexts")); - } -} diff --git a/dspace-rdf/pom.xml b/dspace-rdf/pom.xml index 9c6c154ddc..03959f9cdb 100644 --- a/dspace-rdf/pom.xml +++ b/dspace-rdf/pom.xml @@ -1,11 +1,10 @@ - + 4.0.0 org.dspace dspace-rdf - war + jar DSpace RDF - Parent project for the RDF API and Webapp + DSpace RDF (Linked Data) Extension org.dspace @@ -58,20 +57,17 @@ dspace-services - + - org.springframework - spring-core - - - - org.springframework - spring-context - - - - org.springframework - spring-web + org.springframework.boot + spring-boot-starter + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-logging + + diff --git a/dspace-rdf/src/main/java/org/dspace/app/configuration/RDFWebConfig.java b/dspace-rdf/src/main/java/org/dspace/app/configuration/RDFWebConfig.java new file mode 100644 index 0000000000..15b991af0a --- /dev/null +++ b/dspace-rdf/src/main/java/org/dspace/app/configuration/RDFWebConfig.java @@ -0,0 +1,56 @@ +/** + * 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.configuration; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * RDF webapp configuration. Replaces the old web.xml + *

+ * This @Configuration class is automatically discovered by Spring via a @ComponentScan. + *

+ * All RDF web configurations (beans) can be enabled or disabled by setting "rdf.enabled" + * to true or false, respectively (in your DSpace configuration). Default is "false". + *

+ * All @Value annotated configurations below can also be overridden in your DSpace configuration. + * + * @author Tim Donohue + */ +@Configuration +public class RDFWebConfig { + // Path where RDF should be deployed (when enabled). Defaults to "rdf" + @Value("${rdf.path:rdf}") + private String rdfPath; + + // Servlet Beans. All of the below bean definitions map servlets to respond to specific URL patterns + // These are the combined equivalent of and in web.xml + // All beans are only loaded when rdf.enabled = true + + @Bean + @ConditionalOnProperty("rdf.enabled") + public ServletRegistrationBean rdfSerializationBean() { + ServletRegistrationBean bean = new ServletRegistrationBean( new org.dspace.rdf.providing.DataProviderServlet(), + "/" + rdfPath + "/handle/*"); + bean.setLoadOnStartup(1); + return bean; + } + + @Bean + @ConditionalOnProperty("rdf.enabled") + public ServletRegistrationBean rdfLocalURIRedirectionBean() { + ServletRegistrationBean bean = new ServletRegistrationBean( + new org.dspace.rdf.providing.LocalURIRedirectionServlet(), "/" + rdfPath + "/resource/*"); + bean.setLoadOnStartup(1); + return bean; + } +} + diff --git a/dspace-rdf/src/main/webapp/WEB-INF/web.xml b/dspace-rdf/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index c15ec675f3..0000000000 --- a/dspace-rdf/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - - - RDF Data Provider - - - - The location of the DSpace home directory - - dspace.dir - ${dspace.dir} - - - - log4jConfiguration - ${dspace.dir}/config/log4j2.xml - - The location of the Log4J configuration - - - - - dspace.request - org.dspace.utils.servlet.DSpaceWebappServletFilter - - - - dspace.request - /* - - - - org.dspace.app.util.DSpaceContextListener - - - - org.dspace.servicemanager.servlet.DSpaceKernelServletContextListener - - - - rdf-serialization - org.dspace.rdf.providing.DataProviderServlet - - - - local-uri-redirection - org.dspace.rdf.providing.LocalURIRedirectionServlet - - - - - rdf-serialization - /handle/* - - - - local-uri-redirection - /resource/* - - diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index 6d012d246a..4e5f898032 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -1,5 +1,4 @@ - + 4.0.0 org.dspace dspace-rest diff --git a/dspace-services/pom.xml b/dspace-services/pom.xml index e402bed92b..f85d9d3e29 100644 --- a/dspace-services/pom.xml +++ b/dspace-services/pom.xml @@ -1,6 +1,5 @@ - + 4.0.0 org.dspace dspace-services diff --git a/dspace-spring-rest/pom.xml b/dspace-spring-rest/pom.xml index 0c904bfe60..6b6fbb7911 100644 --- a/dspace-spring-rest/pom.xml +++ b/dspace-spring-rest/pom.xml @@ -1,5 +1,4 @@ - + 4.0.0 org.dspace dspace-spring-rest @@ -28,9 +27,10 @@ @ org.dspace.app.rest.Application - 1.4.4.RELEASE - 2.2.0 - + + 2.4.0 + + 6.2 @@ -256,6 +256,26 @@ org.dspace dspace-services + + + + + org.dspace + dspace-oai + + + org.dspace + dspace-rdf + + + org.dspace + dspace-sword + + + org.dspace + dspace-swordv2 + + org.apache.commons commons-collections4 @@ -267,7 +287,7 @@ com.nimbusds nimbus-jose-jwt - 6.2 + ${nimbus-jose-jwt.version} org.apache.solr @@ -289,13 +309,18 @@ org.json json + + + com.jayway.jsonpath + json-path + com.jayway.jsonpath json-path - test ${json-path.version} + test com.jayway.jsonpath diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/Application.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/Application.java index 3e8328d478..0315860178 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/Application.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/Application.java @@ -14,6 +14,7 @@ import org.dspace.app.rest.filter.DSpaceRequestContextFilter; import org.dspace.app.rest.model.hateoas.DSpaceRelProvider; import org.dspace.app.rest.parameter.resolver.SearchFilterResolver; import org.dspace.app.rest.utils.ApplicationConfig; +import org.dspace.app.rest.utils.DSpaceConfigurationInitializer; import org.dspace.app.rest.utils.DSpaceKernelInitializer; import org.dspace.app.util.DSpaceContextListener; import org.dspace.utils.servlet.DSpaceWebappServletFilter; @@ -22,7 +23,6 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.boot.web.support.SpringBootServletInitializer; import org.springframework.context.annotation.Bean; import org.springframework.core.annotation.Order; @@ -62,7 +62,6 @@ public class Application extends SpringBootServletInitializer { * This is necessary to allow us to build a deployable WAR, rather than * always relying on embedded Tomcat. *

- *

* See: http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-create-a-deployable-war-file * * @param application @@ -70,13 +69,10 @@ public class Application extends SpringBootServletInitializer { */ @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + // Pass this Application class, and our initializers for DSpace Kernel and Configuration + // NOTE: Kernel must be initialized before Configuration return application.sources(Application.class) - .initializers(new DSpaceKernelInitializer()); - } - - @Bean - public ServletContextInitializer contextInitializer() { - return servletContext -> servletContext.setInitParameter("dspace.dir", configuration.getDspaceHome()); + .initializers(new DSpaceKernelInitializer(), new DSpaceConfigurationInitializer()); } /** @@ -89,7 +85,6 @@ public class Application extends SpringBootServletInitializer { @Order(2) protected DSpaceContextListener dspaceContextListener() { // This listener initializes the DSpace Context object - // (and loads all DSpace configs) return new DSpaceContextListener(); } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/RelationshipRestController.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RelationshipRestController.java new file mode 100644 index 0000000000..7f6a1f22cf --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RelationshipRestController.java @@ -0,0 +1,133 @@ +/** + * 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 java.util.LinkedList; +import java.util.List; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.converter.RelationshipConverter; +import org.dspace.app.rest.link.HalLinkService; +import org.dspace.app.rest.model.RelationshipRest; +import org.dspace.app.rest.model.RelationshipRestWrapper; +import org.dspace.app.rest.model.hateoas.RelationshipResourceWrapper; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.rest.utils.Utils; +import org.dspace.content.Item; +import org.dspace.content.Relationship; +import org.dspace.content.RelationshipType; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.RelationshipService; +import org.dspace.content.service.RelationshipTypeService; +import org.dspace.core.Context; +import org.dspace.util.UUIDUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * This will be the entry point for the api/core/relationships endpoint with additional paths to it + */ +@RestController +@RequestMapping("/api/core/relationships") +public class RelationshipRestController { + + /** + * Regular expression in the request mapping to accept a string as identifier but not the other kind of + * identifier (digits or uuid) + */ + private static final String REGEX_REQUESTMAPPING_LABEL = "/{label:^(?!^\\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+\\-]+$+}"; + + @Autowired + private RelationshipTypeService relationshipTypeService; + + @Autowired + private RelationshipService relationshipService; + + @Autowired + private ItemService itemService; + + @Autowired + private RelationshipConverter relationshipConverter; + + @Autowired + Utils utils; + + @Autowired + private HalLinkService halLinkService; + + /** + * This method will retrieve all the Relationships that have a RelationshipType which has a left or right label + * equal to the one passed along in the pathvariable. + * This is further filtered by an optional dso parameter to filter on only the relationships for the given dso + * if this is applicable + * + * @param response The response object + * @param request The request object + * @param label The label on which the Relationship's RelationshipType will be matched + * @param dsoId The ID of the dso on which we'll search for relationships if applicable + * @param pageable The page object + * @return A Resource containing all the relationships that meet the criteria + * @throws Exception If something goes wrong + */ + @RequestMapping(method = RequestMethod.GET, value = REGEX_REQUESTMAPPING_LABEL) + public RelationshipResourceWrapper retrieveByLabel(HttpServletResponse response, + HttpServletRequest request, @PathVariable String label, + @RequestParam(name = "dso", required = false) String dsoId, + Pageable pageable) + throws Exception { + + Context context = ContextUtil.obtainContext(request); + + List relationshipTypeList = relationshipTypeService.findByLeftOrRightLabel(context, label); + List relationships = new LinkedList<>(); + if (StringUtils.isNotBlank(dsoId)) { + + UUID uuid = UUIDUtils.fromString(dsoId); + Item item = itemService.find(context, uuid); + + if (item == null) { + throw new ResourceNotFoundException("The request DSO with id: " + dsoId + " was not found"); + } + for (RelationshipType relationshipType : relationshipTypeList) { + relationships.addAll(relationshipService.findByItemAndRelationshipType(context, + item, relationshipType)); + } + } else { + for (RelationshipType relationshipType : relationshipTypeList) { + relationships.addAll(relationshipService.findByRelationshipType(context, relationshipType)); + } + } + + List relationshipRests = new LinkedList<>(); + for (Relationship relationship : relationships) { + relationshipRests.add(relationshipConverter.fromModel(relationship)); + } + + RelationshipRestWrapper relationshipRestWrapper = new RelationshipRestWrapper(); + relationshipRestWrapper.setLabel(label); + relationshipRestWrapper.setDsoId(dsoId); + relationshipRestWrapper.setRelationshipRestList(relationshipRests); + + RelationshipResourceWrapper relationshipResourceWrapper = new RelationshipResourceWrapper( + relationshipRestWrapper, utils, relationshipRests.size(), pageable); + + halLinkService.addLinks(relationshipResourceWrapper, pageable); + return relationshipResourceWrapper; + } + +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/RelationshipTypeRestController.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RelationshipTypeRestController.java new file mode 100644 index 0000000000..f6f7a28067 --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RelationshipTypeRestController.java @@ -0,0 +1,93 @@ +/** + * 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 java.sql.SQLException; +import java.util.LinkedList; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.dspace.app.rest.converter.RelationshipTypeConverter; +import org.dspace.app.rest.link.HalLinkService; +import org.dspace.app.rest.model.RelationshipTypeRest; +import org.dspace.app.rest.model.RelationshipTypeRestWrapper; +import org.dspace.app.rest.model.hateoas.RelationshipTypeResourceWrapper; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.rest.utils.Utils; +import org.dspace.content.EntityType; +import org.dspace.content.RelationshipType; +import org.dspace.content.service.EntityTypeService; +import org.dspace.content.service.RelationshipTypeService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * This controller will handle all the incoming calls on the api/core/entitytypes/{id}/relationshiptypes endpoint + * where the id parameter can be filled in to match a specific entityType and then get all the relationshipTypes + * for the given EntityType + */ +@RestController +@RequestMapping("/api/core/entitytypes/{id}/relationshiptypes") +public class RelationshipTypeRestController { + + + @Autowired + private RelationshipTypeService relationshipTypeService; + + @Autowired + private EntityTypeService entityTypeService; + + @Autowired + private RelationshipTypeConverter relationshipTypeConverter; + + @Autowired + private Utils utils; + + @Autowired + private HalLinkService halLinkService; + + /** + * This method will retrieve all the RelationshipTypes that conform to the given EntityType by the given ID and + * it will return this in a wrapped resource. + * + * @param id The ID of the EntityType objects that we'll use to retrieve the RelationshipTypes + * @param response The response object + * @param request The request object + * @return The wrapped resource containing the list of RelationshipType objects as defined above + * @throws SQLException If something goes wrong + */ + @RequestMapping(method = RequestMethod.GET) + public RelationshipTypeResourceWrapper retrieve(@PathVariable Integer id, HttpServletResponse response, + HttpServletRequest request) throws SQLException { + Context context = ContextUtil.obtainContext(request); + EntityType entityType = entityTypeService.find(context, id); + List list = relationshipTypeService.findByEntityType(context, entityType); + + List relationshipTypeRests = new LinkedList<>(); + + for (RelationshipType relationshipType : list) { + relationshipTypeRests.add(relationshipTypeConverter.fromModel(relationshipType)); + } + + + RelationshipTypeRestWrapper relationshipTypeRestWrapper = new RelationshipTypeRestWrapper(); + relationshipTypeRestWrapper.setEntityTypeId(id); + relationshipTypeRestWrapper.setEntityTypeLabel(entityType.getLabel()); + relationshipTypeRestWrapper.setRelationshipTypeRestList(relationshipTypeRests); + + RelationshipTypeResourceWrapper relationshipTypeResourceWrapper = new RelationshipTypeResourceWrapper( + relationshipTypeRestWrapper, utils); + halLinkService.addLinks(relationshipTypeResourceWrapper); + return relationshipTypeResourceWrapper; + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java index 92d4b0a26d..c5b84c810d 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java @@ -378,38 +378,64 @@ public class RestResourceController implements InitializingBean { /** * Execute a POST request; * - * curl -X POST http:///dspace-spring-rest/api/{apiCategory}/{model} + * curl -X POST -H "Content-Type:application/json" http:///dspace-spring-rest/api/{apiCategory}/{model} * * Example: *

      * {@code
-     *      curl -X POST http:///dspace-spring-rest/api/submission/workspaceitems
+     *      curl -X POST -H "Content-Type:application/json" http:///dspace-spring-rest/api/submission/workspaceitems
      * }
      * 
* - * @param request - * @param apiCategory - * @param model - * @return - * @throws HttpRequestMethodNotSupportedException + * @param request The relevant request + * @param apiCategory The apiCategory to be used + * @param model The model to be used + * @return The relevant ResponseEntity for this request + * @throws HttpRequestMethodNotSupportedException If something goes wrong */ - @RequestMapping(method = RequestMethod.POST) + @RequestMapping(method = RequestMethod.POST, consumes = {"application/json", "application/hal+json"}) public ResponseEntity post(HttpServletRequest request, @PathVariable String apiCategory, @PathVariable String model) throws HttpRequestMethodNotSupportedException { - return postInternal(request, apiCategory, model); + return postJsonInternal(request, apiCategory, model); } /** - * Internal method to execute POST; + * Execute a POST request; * - * @param request - * @param apiCategory - * @param model - * @return - * @throws HttpRequestMethodNotSupportedException + * curl -X POST -H "Content-Type:text/uri-list" http:///dspace-spring-rest/api/{apiCategory}/{model} + * + * Example: + *
+     * {@code
+     *      curl -X POST -H "Content-Type:text/uri-list" http:///dspace-spring-rest/api/submission/workspaceitems
+     * }
+     * 
+ * + * @param request The relevant request + * @param apiCategory The apiCategory to be used + * @param model The model to be used + * @return The relevant ResponseEntity for this request + * @throws HttpRequestMethodNotSupportedException If something goes wrong */ - public ResponseEntity postInternal(HttpServletRequest request, + @RequestMapping(method = RequestMethod.POST, consumes = {"text/uri-list"}) + public ResponseEntity postWithUriListContentType(HttpServletRequest request, + @PathVariable String apiCategory, + @PathVariable String model) + throws HttpRequestMethodNotSupportedException { + return postUriListInternal(request, apiCategory, model); + } + + /** + * Internal method to execute POST with application/json MediaType; + * + * @param request The relevant request + * @param apiCategory The apiCategory to be used + * @param model The model to be used + * @return The relevant ResponseEntity for this request + * @throws HttpRequestMethodNotSupportedException If something goes wrong + */ + public ResponseEntity postJsonInternal(HttpServletRequest request, String apiCategory, String model) throws HttpRequestMethodNotSupportedException { @@ -425,6 +451,40 @@ public class RestResourceController implements InitializingBean { return ControllerUtils.toResponseEntity(HttpStatus.CREATED, null, result); } + /** + * Internal method to execute POST with text/uri-list MediaType; + * + * @param request The relevant request + * @param apiCategory The apiCategory to be used + * @param model The model to be used + * @return The relevant ResponseEntity for this request + * @throws HttpRequestMethodNotSupportedException If something goes wrong + */ + public ResponseEntity postUriListInternal(HttpServletRequest request, + String apiCategory, + String model) + throws HttpRequestMethodNotSupportedException { + checkModelPluralForm(apiCategory, model); + DSpaceRestRepository repository = utils.getResourceRepository(apiCategory, model); + RestAddressableModel modelObject = null; + List stringListFromRequest = utils.getStringListFromRequest(request); + try { + modelObject = repository.createAndReturn(stringListFromRequest); + } catch (ClassCastException e) { + log.error("Something went wrong whilst creating the object for apiCategory: " + apiCategory + + " and model: " + model, e); + return ControllerUtils.toEmptyResponse(HttpStatus.INTERNAL_SERVER_ERROR); + } + if (modelObject == null) { + throw new HttpRequestMethodNotSupportedException(RequestMethod.POST.toString()); + } + DSpaceResource result = repository.wrapResource(modelObject); + linkService.addLinks(result); + //TODO manage HTTPHeader + return ControllerUtils.toResponseEntity(HttpStatus.CREATED, null, result); + } + + /** * Called in POST, with a x-www-form-urlencoded, execute an action on a resource * @@ -1020,35 +1080,6 @@ public class RestResourceController implements InitializingBean { return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); } - - - /** - * Execute a PUT request for an entity with id of type Integer; - * - * curl -X PUT http:///api/{apiCategory}/{model}/{id} - * - * Example: - *
-     * {@code
-     *      curl -X PUT http:///api/core/metadatafield/1
-     * }
-     * 
- * - * @param request the http request - * @param apiCategory the API category e.g. "core" - * @param model the DSpace model e.g. "metadatafield" - * @param id the ID of the target REST object - * @param jsonNode the part of the request body representing the updated rest object - * @return the relevant REST resource - */ - @RequestMapping(method = RequestMethod.PUT, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_DIGIT) - public DSpaceResource put(HttpServletRequest request, - @PathVariable String apiCategory, @PathVariable String model, - @PathVariable Integer id, - @RequestBody JsonNode jsonNode) { - return putOneInternal(request, apiCategory, model, id, jsonNode); - } - /** * Execute a PUT request for an entity with id of type UUID; * @@ -1073,32 +1104,110 @@ public class RestResourceController implements InitializingBean { @PathVariable String apiCategory, @PathVariable String model, @PathVariable UUID uuid, @RequestBody JsonNode jsonNode) { - return putOneInternal(request, apiCategory, model, uuid, jsonNode); + return putOneJsonInternal(request, apiCategory, model, uuid, jsonNode); } /** - * Internal method to update a single entity + * Execute a PUT request for an entity with id of type Integer; + * + * curl -X PUT -H "Content-Type:application/json" http:///dspace-spring-rest/api/{apiCategory}/{model}/{id} + * + * Example: + *
+     * {@code
+     *      curl -X PUT -H "Content-Type:application/json" http:///dspace-spring-rest/api/core/metadatafield/1
+     * }
+     * 
* * @param request the http request * @param apiCategory the API category e.g. "api" - * @param model the DSpace model e.g. "metadatafield" - * @param uuid the ID of the target REST object + * @param model the DSpace model e.g. "collection" + * @param id the ID of the target REST object * @param jsonNode the part of the request body representing the updated rest object * @return the relevant REST resource */ - private DSpaceResource putOneInternal(HttpServletRequest request, - String apiCategory, - String model, ID uuid, - JsonNode jsonNode) { + @RequestMapping(method = RequestMethod.PUT, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_DIGIT, + consumes = {"application/json", "application/hal+json"}) + public DSpaceResource put(HttpServletRequest request, + @PathVariable String apiCategory, @PathVariable String model, + @PathVariable Integer id, + @RequestBody(required = true) JsonNode jsonNode) { + return putOneJsonInternal(request, apiCategory, model, id, jsonNode); + } + + /** + * Execute a PUT request for an entity with id of type Integer; + * + * curl -X PUT -H "Content-Type:text/uri-list" http:///dspace-spring-rest/api/{apiCategory}/{model}/{id} + * + * Example: + *
+     * {@code
+     *      curl -X PUT -H "Content-Type:text/uri-list" http:///dspace-spring-rest/api/core/metadatafield/1
+     * }
+     * 
+ * + * @param request the http request + * @param apiCategory the API category e.g. "api" + * @param model the DSpace model e.g. "collection" + * @param id the ID of the target REST object + * @return the relevant REST resource + */ + @RequestMapping(method = RequestMethod.PUT, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_DIGIT, + consumes = {"text/uri-list"}) + public DSpaceResource put(HttpServletRequest request, + @PathVariable String apiCategory, @PathVariable String model, + @PathVariable Integer id) throws IOException { + return putOneUriListInternal(request, apiCategory, model, id); + } + + /** + * Internal method to execute PUT with application/json MediaType; + * + * @param request The relevant request + * @param apiCategory The apiCategory to be used + * @param model The model to be used + * @param id The ID for the resource to be altered by the PUT + * @param jsonNode The relevant JsonNode to be used by the PUT + * @return The relevant DSpaceResource for this request + */ + private DSpaceResource putOneJsonInternal( + HttpServletRequest request, String apiCategory, String model, ID id, JsonNode jsonNode) { checkModelPluralForm(apiCategory, model); DSpaceRestRepository repository = utils.getResourceRepository(apiCategory, model); RestAddressableModel modelObject = null; - modelObject = repository.put(request, apiCategory, model, uuid, jsonNode); + modelObject = repository.put(request, apiCategory, model, id, jsonNode); if (modelObject == null) { - throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + uuid + " not found"); + throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + id + " not found"); } DSpaceResource result = repository.wrapResource(modelObject); linkService.addLinks(result); return result; + + } + /** + * Internal method to execute PUT with text/uri-list MediaType; + * + * @param request The relevant request + * @param apiCategory The apiCategory to be used + * @param model The model to be used + * @param id The ID for the resource to be altered by the PUT + * @return The relevant DSpaceResource for this request + * @throws IOException If something goes wrong + */ + private DSpaceResource putOneUriListInternal( + HttpServletRequest request, String apiCategory, String model, ID id) throws IOException { + checkModelPluralForm(apiCategory, model); + DSpaceRestRepository repository = utils.getResourceRepository(apiCategory, model); + RestAddressableModel modelObject = null; + List stringList = utils.getStringListFromRequest(request); + modelObject = repository.put(request, apiCategory, model, id, stringList); + if (modelObject == null) { + throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + id + " not found"); + } + DSpaceResource result = repository.wrapResource(modelObject); + linkService.addLinks(result); + return result; + } } \ No newline at end of file diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/RootRestResourceController.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RootRestResourceController.java index 18f41310b3..0f8d770b37 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/RootRestResourceController.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RootRestResourceController.java @@ -48,5 +48,4 @@ public class RootRestResourceController { return rootResource; } - -} \ No newline at end of file +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/EntityTypeConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/EntityTypeConverter.java new file mode 100644 index 0000000000..ae2ef646c5 --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/EntityTypeConverter.java @@ -0,0 +1,46 @@ +/** + * 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.converter; + +import org.dspace.app.rest.model.EntityTypeRest; +import org.dspace.content.EntityType; +import org.springframework.stereotype.Component; + +/** + * This converter is responsible for transforming the model representation of an EntityType to the REST + * representation of an EntityType and vice versa + */ +@Component +public class EntityTypeConverter implements DSpaceConverter { + + /** + * This method converts the EntityType model object that is passed along in the params to the + * REST representation of this object + * @param obj The EntityType model object to be converted + * @return The EntityType REST object that is made from the model object + */ + public EntityTypeRest fromModel(EntityType obj) { + EntityTypeRest entityTypeRest = new EntityTypeRest(); + entityTypeRest.setId(obj.getID()); + entityTypeRest.setLabel(obj.getLabel()); + return entityTypeRest; + } + + /** + * This method converts the EntityType REST object that is passed along in the params to the model + * representation of this object + * @param obj The EntityType REST object to be converted + * @return The EntityType model object that is made from the REST object + */ + public EntityType toModel(EntityTypeRest obj) { + EntityType entityType = new EntityType(); + entityType.setId(obj.getId()); + entityType.setLabel(obj.getLabel()); + return entityType; + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/FilteredDiscoveryPageConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/FilteredDiscoveryPageConverter.java new file mode 100644 index 0000000000..2194753a54 --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/FilteredDiscoveryPageConverter.java @@ -0,0 +1,45 @@ +/** + * 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.converter; + +import org.dspace.app.rest.model.FilteredDiscoveryPageRest; +import org.dspace.content.EntityType; +import org.dspace.content.virtual.EntityTypeToFilterQueryService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * This converter takes an EntityType and converts it to a FilteredDiscoveryPageRest object to give a + * representation about the filter query that has to be used for the given EntityType + */ +@Component +public class FilteredDiscoveryPageConverter implements DSpaceConverter { + @Autowired + private EntityTypeToFilterQueryService entityTypeToFilterQueryService; + + /** + * This method converts the EntityType object to a FilteredDiscoveryPageRest object to be passed along + * to the resource and endpoint so that callers can know what filter query they need to use to + * filter on a particular, given, EntityType + * @param obj The EntityType for which this filterQuery string will be looked up for + * @return The filterQuery String for the given EntityType + */ + public FilteredDiscoveryPageRest fromModel(EntityType obj) { + FilteredDiscoveryPageRest filteredDiscoveryPageRest = new FilteredDiscoveryPageRest(); + filteredDiscoveryPageRest.setId(obj.getLabel()); + filteredDiscoveryPageRest.setLabel(obj.getLabel()); + filteredDiscoveryPageRest.setFilterQueryString( + entityTypeToFilterQueryService.getFilterQueryForKey(obj.getLabel())); + return filteredDiscoveryPageRest; + } + + public EntityType toModel(FilteredDiscoveryPageRest obj) { + return new EntityType(); + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ItemConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ItemConverter.java index 5db586013d..cbf4c20479 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ItemConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ItemConverter.java @@ -7,17 +7,29 @@ */ package org.dspace.app.rest.converter; +import java.sql.SQLException; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; +import javax.servlet.http.HttpServletRequest; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.model.RelationshipRest; +import org.dspace.app.rest.utils.ContextUtil; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.Relationship; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.RelationshipService; +import org.dspace.core.Context; import org.dspace.discovery.IndexableObject; +import org.dspace.services.RequestService; +import org.dspace.services.model.Request; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -36,6 +48,16 @@ public class ItemConverter private CollectionConverter collectionConverter; @Autowired(required = true) private BitstreamConverter bitstreamConverter; + @Autowired + private RequestService requestService; + @Autowired + private RelationshipService relationshipService; + @Autowired + private RelationshipConverter relationshipConverter; + @Autowired + private ItemService itemService; + @Autowired + private MetadataConverter metadataConverter; private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemConverter.class); @@ -70,6 +92,33 @@ public class ItemConverter } } item.setBitstreams(bitstreams); + List relationships = new LinkedList<>(); + try { + Context context; + Request currentRequest = requestService.getCurrentRequest(); + if (currentRequest != null) { + HttpServletRequest request = currentRequest.getHttpServletRequest(); + context = ContextUtil.obtainContext(request); + } else { + context = new Context(); + } + relationships = relationshipService.findByItem(context, obj); + } catch (SQLException e) { + log.error("Error retrieving relationships for item " + item.getHandle(), e); + } + List relationshipRestList = new LinkedList<>(); + for (Relationship relationship : relationships) { + RelationshipRest relationshipRest = relationshipConverter.fromModel(relationship); + relationshipRestList.add(relationshipRest); + } + item.setRelationships(relationshipRestList); + + List fullList = new LinkedList<>(); + fullList = itemService.getMetadata(obj, Item.ANY, Item.ANY, Item.ANY, Item.ANY, true); + + item.setMetadata(metadataConverter.convert(fullList)); + + return item; } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RelationshipConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RelationshipConverter.java new file mode 100644 index 0000000000..2e46b28341 --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RelationshipConverter.java @@ -0,0 +1,53 @@ +/** + * 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.converter; + +import org.dspace.app.rest.model.RelationshipRest; +import org.dspace.content.Relationship; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; + +/** + * This converter is responsible for transforming the model representation of an Relationship to the REST + * representation of an Relationship and vice versa + */ +@Component +public class RelationshipConverter implements DSpaceConverter { + + @Autowired + private RelationshipTypeConverter relationshipTypeConverter; + + + /** + * This method converts the Relationship model object that is passed along in the params to the + * REST representation of this object + * @param obj The Relationship model object to be converted + * @return The Relationship REST object that is made from the model object + */ + public RelationshipRest fromModel(Relationship obj) { + RelationshipRest relationshipRest = new RelationshipRest(); + relationshipRest.setId(obj.getID()); + relationshipRest.setLeftId(obj.getLeftItem().getID()); + relationshipRest.setRelationshipType(relationshipTypeConverter.fromModel(obj.getRelationshipType())); + relationshipRest.setRightId(obj.getRightItem().getID()); + relationshipRest.setLeftPlace(obj.getLeftPlace()); + relationshipRest.setRightPlace(obj.getRightPlace()); + return relationshipRest; + } + + /** + * This method converts the Relationship REST object that is passed along in the params to the model + * representation of this object + * @param obj The Relationship REST object to be converted + * @return The Relationship model object that is made from the REST object + */ + public Relationship toModel(RelationshipRest obj) { + throw new NotImplementedException(); + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RelationshipTypeConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RelationshipTypeConverter.java new file mode 100644 index 0000000000..fa8dcc8a1e --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/RelationshipTypeConverter.java @@ -0,0 +1,57 @@ +/** + * 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.converter; + +import org.dspace.app.rest.model.RelationshipTypeRest; +import org.dspace.content.RelationshipType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * This converter is responsible for transforming the model representation of an RelationshipType to the REST + * representation of an RelationshipType and vice versa + */ +@Component +public class RelationshipTypeConverter implements DSpaceConverter { + + @Autowired + private EntityTypeConverter entityTypeConverter; + + /** + * This method converts the RelationshipType model object that is passed along in the params to the + * REST representation of this object + * @param obj The RelationshipType model object to be converted + * @return The RelationshipType REST object that is made from the model object + */ + public RelationshipTypeRest fromModel(RelationshipType obj) { + RelationshipTypeRest relationshipTypeRest = new RelationshipTypeRest(); + + relationshipTypeRest.setId(obj.getID()); + relationshipTypeRest.setLeftLabel(obj.getLeftLabel()); + relationshipTypeRest.setRightLabel(obj.getRightLabel()); + relationshipTypeRest.setLeftMinCardinality(obj.getLeftMinCardinality()); + relationshipTypeRest.setLeftMaxCardinality(obj.getLeftMaxCardinality()); + relationshipTypeRest.setRightMinCardinality(obj.getRightMinCardinality()); + relationshipTypeRest.setRightMaxCardinality(obj.getRightMaxCardinality()); + relationshipTypeRest.setLeftType(entityTypeConverter.fromModel(obj.getLeftType())); + relationshipTypeRest.setRightType(entityTypeConverter.fromModel(obj.getRightType())); + + return relationshipTypeRest; + } + + /** + * This method converts the RelationshipType REST object that is passed along in the params to the model + * representation of this object + * @param obj The RelationshipType REST object to be converted + * @return The RelationshipType model object that is made from the REST object + */ + public RelationshipType toModel(RelationshipTypeRest obj) { + + return null; + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/link/relation/EntityTypeHalLinkFactory.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/link/relation/EntityTypeHalLinkFactory.java new file mode 100644 index 0000000000..99f738ba1e --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/link/relation/EntityTypeHalLinkFactory.java @@ -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.app.rest.link.relation; + +import java.util.LinkedList; + +import org.dspace.app.rest.RelationshipTypeRestController; +import org.dspace.app.rest.link.HalLinkFactory; +import org.dspace.app.rest.model.hateoas.EntityTypeResource; +import org.springframework.data.domain.Pageable; +import org.springframework.hateoas.Link; +import org.springframework.stereotype.Component; + +/** + * This class' purpose is to add the links to the EntityTypeResource. This function and class will be called + * and used + * when the HalLinkService addLinks methods is called as it'll iterate over all the different factories and check + * whether + * these are allowed to create links for said resource or not. + */ +@Component +public class EntityTypeHalLinkFactory extends HalLinkFactory { + @Override + protected void addLinks(EntityTypeResource halResource, Pageable pageable, LinkedList list) throws Exception { + list.add(buildLink("relationshiptypes", getMethodOn().retrieve(halResource.getContent().getId(), null, null))); + } + + @Override + protected Class getControllerClass() { + return RelationshipTypeRestController.class; + } + + @Override + protected Class getResourceClass() { + return EntityTypeResource.class; + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/link/relation/RelationshipHalLinkFactory.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/link/relation/RelationshipHalLinkFactory.java new file mode 100644 index 0000000000..9da50be395 --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/link/relation/RelationshipHalLinkFactory.java @@ -0,0 +1,47 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.link.relation; + +import java.util.LinkedList; + +import org.atteo.evo.inflector.English; +import org.dspace.app.rest.RestResourceController; +import org.dspace.app.rest.link.HalLinkFactory; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.model.hateoas.RelationshipResource; +import org.springframework.data.domain.Pageable; +import org.springframework.hateoas.Link; +import org.springframework.stereotype.Component; + +/** + * This class adds links to {@link org.dspace.app.rest.model.hateoas.RelationshipResource}s + * This builds a link to both items included in the relationship + */ +@Component +public class RelationshipHalLinkFactory extends HalLinkFactory { + @Override + protected void addLinks(RelationshipResource halResource, Pageable pageable, LinkedList list) + throws Exception { + + list.add(buildLink("leftItem", getMethodOn() + .findOne(ItemRest.CATEGORY, English.plural(ItemRest.NAME), halResource.getContent().getLeftId(), null))); + + list.add(buildLink("rightItem", getMethodOn() + .findOne(ItemRest.CATEGORY, English.plural(ItemRest.NAME), halResource.getContent().getRightId(), null))); + } + + @Override + protected Class getControllerClass() { + return RestResourceController.class; + } + + @Override + protected Class getResourceClass() { + return RelationshipResource.class; + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/link/relation/RelationshipResourceWrapperHalLinkFactory.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/link/relation/RelationshipResourceWrapperHalLinkFactory.java new file mode 100644 index 0000000000..36fb3e3f4d --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/link/relation/RelationshipResourceWrapperHalLinkFactory.java @@ -0,0 +1,75 @@ +/** + * 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.relation; + +import java.util.LinkedList; + +import org.dspace.app.rest.RelationshipRestController; +import org.dspace.app.rest.link.HalLinkFactory; +import org.dspace.app.rest.model.RelationshipRest; +import org.dspace.app.rest.model.RelationshipRestWrapper; +import org.dspace.app.rest.model.hateoas.EmbeddedPage; +import org.dspace.app.rest.model.hateoas.RelationshipResourceWrapper; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.hateoas.Link; +import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; + +/** + * This class' purpose is to add the links to the RelationshipResourceWrapper. This function and class will be called + * and used + * when the HalLinkService addLinks methods is called as it'll iterate over all the different factories and check + * whether + * these are allowed to create links for said resource or not. + */ +@Component +public class RelationshipResourceWrapperHalLinkFactory + extends HalLinkFactory { + @Override + protected void addLinks(RelationshipResourceWrapper halResource, Pageable pageable, LinkedList list) + throws Exception { + + PageImpl page = new PageImpl<>(halResource.getContent().getRelationshipRestList(), pageable, + halResource.getContent().getRelationshipRestList().size()); + + halResource.setPageHeader(new EmbeddedPage(getSelfLink(halResource.getContent(), pageable), + page, halResource.getContent().getRelationshipRestList(), + true, "relationships")); + } + + /** + * This method will construct a self link to the RelationshipRestController.retrieveByLabel method. + * This will be constructed so that the RelationshipResourceWrapper resource can contain this selflink + * and immediately point to the correct endpoint with it. + * @param content The RelationshipRestWrapper from which we'll retrieve variables to construct the link + * @param pageable The page object + * @return The String determining the link to the correct endpoint + * @throws Exception If something goes wrong + */ + public String getSelfLink(RelationshipRestWrapper content, Pageable pageable) throws Exception { + if (content != null) { + UriComponentsBuilder uriBuilderSelfLink = uriBuilder(getMethodOn() + .retrieveByLabel(null, null, + content.getLabel(), + content.getDsoId(), pageable)); + return uriBuilderSelfLink.build().toString(); + } + return null; + } + + @Override + protected Class getControllerClass() { + return RelationshipRestController.class; + } + + @Override + protected Class getResourceClass() { + return RelationshipResourceWrapper.class; + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/link/relation/RelationshipTypeResourceWrapperHalLinkFactory.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/link/relation/RelationshipTypeResourceWrapperHalLinkFactory.java new file mode 100644 index 0000000000..f3dccbddbd --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/link/relation/RelationshipTypeResourceWrapperHalLinkFactory.java @@ -0,0 +1,45 @@ +/** + * 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.relation; + +import java.util.LinkedList; + +import org.dspace.app.rest.RelationshipTypeRestController; +import org.dspace.app.rest.link.HalLinkFactory; +import org.dspace.app.rest.model.hateoas.RelationshipTypeResourceWrapper; +import org.springframework.data.domain.Pageable; +import org.springframework.hateoas.Link; +import org.springframework.stereotype.Component; + +/** + * This class' purpose is to add the links to the RelationshipTypeResourceWrapper. + * This function and class will be called and used + * when the HalLinkService addLinks methods is called as it'll iterate over all the different factories and check + * whether + * these are allowed to create links for said resource or not. + */ +@Component +public class RelationshipTypeResourceWrapperHalLinkFactory + extends HalLinkFactory { + @Override + protected void addLinks(RelationshipTypeResourceWrapper halResource, Pageable pageable, LinkedList list) + throws Exception { + list.add(buildLink(Link.REL_SELF, getMethodOn() + .retrieve(halResource.getContent().getEntityTypeId(), null, null))); + } + + @Override + protected Class getControllerClass() { + return RelationshipTypeRestController.class; + } + + @Override + protected Class getResourceClass() { + return RelationshipTypeResourceWrapper.class; + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java new file mode 100644 index 0000000000..fecd065815 --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/EntityTypeRest.java @@ -0,0 +1,43 @@ +/** + * 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 org.dspace.app.rest.RestResourceController; + +/** + * This class is the REST representation of the EntityType model object and acts as a data object + * for the EntityTypeResource class. + * Refer to {@link org.dspace.content.EntityType} for explanation of the properties + */ +public class EntityTypeRest extends BaseObjectRest { + + public static final String NAME = "entitytype"; + public static final String CATEGORY = "core"; + + public String getCategory() { + return CATEGORY; + } + + public Class getController() { + return RestResourceController.class; + } + + public String getType() { + return NAME; + } + + private String label; + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/FilteredDiscoveryPageRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/FilteredDiscoveryPageRest.java new file mode 100644 index 0000000000..c9686b3023 --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/FilteredDiscoveryPageRest.java @@ -0,0 +1,58 @@ +/** + * 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 com.fasterxml.jackson.annotation.JsonProperty; +import org.dspace.app.rest.RestResourceController; + +/** + * This class acts as the REST representation of the converted EntityType objects to this logic. This class acts + * as a data holder for the FilteredDiscoveryPageResource + */ +public class FilteredDiscoveryPageRest extends BaseObjectRest { + + public static final String NAME = "filtered-discovery-page"; + public static final String CATEGORY = "config"; + + public String getCategory() { + return CATEGORY; + } + + public Class getController() { + return RestResourceController.class; + } + + public String getType() { + return NAME; + } + + /** + * The label of the filter + */ + @JsonProperty(value = "filter-name") + private String label; + /** + * The filterQuery string that can be used to filter on the label + */ + @JsonProperty(value = "discovery-query") + private String filterQueryString; + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public void setFilterQueryString(String filterQueryString) { + this.filterQueryString = filterQueryString; } + + public String getFilterQueryString() { + return this.filterQueryString; } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/ItemRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/ItemRest.java index 3dcaa02fe7..abe1d3473f 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/ItemRest.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/ItemRest.java @@ -31,6 +31,8 @@ public class ItemRest extends DSpaceObjectRest { private CollectionRest templateItemOf; List bitstreams; + List relationships; + @Override public String getCategory() { return CATEGORY; @@ -99,4 +101,14 @@ public class ItemRest extends DSpaceObjectRest { public void setBitstreams(List bitstreams) { this.bitstreams = bitstreams; } + + @LinkRest(linkClass = RelationshipRest.class) + @JsonIgnore + public List getRelationships() { + return relationships; + } + + public void setRelationships(List relationships) { + this.relationships = relationships; + } } \ No newline at end of file diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataValueRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataValueRest.java index 013e235847..aeffb6c04d 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataValueRest.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataValueRest.java @@ -7,10 +7,6 @@ */ package org.dspace.app.rest.model; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import org.dspace.app.rest.converter.MetadataConverter; /** * An embeddable representation of the Metadata to use in with DSpace REST @@ -28,17 +24,6 @@ public class MetadataValueRest { int confidence; - /** - * The order of this metadata value with respect to others in the same DSO with the same key. - * - * In the REST representation, all values of the same key are given as a json array that expresses - * their relative order, so there is no need to expose the exact numeric value publicly. The numeric - * value is only used at this level to ensure the intended order is respected when converting to/from json. - * - * @see MetadataConverter#convert(List) - * @see MetadataRest#put(String, MetadataValueRest...) - */ - @JsonIgnore int place = -1; public MetadataValueRest() { diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/RelationshipRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/RelationshipRest.java new file mode 100644 index 0000000000..51ae251121 --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/RelationshipRest.java @@ -0,0 +1,92 @@ +/** + * 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.UUID; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.dspace.app.rest.RestResourceController; + +/** + * This class acts as the REST representation of the Relationship model class. + * This class acts as a data holder for the RelationshipResource + * Refer to {@link org.dspace.content.Relationship} for explanation about the properties + */ +public class RelationshipRest extends BaseObjectRest { + public static final String NAME = "relationship"; + public static final String CATEGORY = "core"; + + private UUID leftId; + private int relationshipTypeId; + private RelationshipTypeRest relationshipType; + private UUID rightId; + private int leftPlace; + private int rightPlace; + + public String getType() { + return NAME; + } + + public String getCategory() { + return CATEGORY; + } + + public Class getController() { + return RestResourceController.class; + } + + public UUID getLeftId() { + return leftId; + } + + public void setLeftId(UUID leftId) { + this.leftId = leftId; + } + + @LinkRest(linkClass = RelationshipTypeRest.class) + @JsonIgnore + public RelationshipTypeRest getRelationshipType() { + return relationshipType; + } + + public void setRelationshipType(RelationshipTypeRest relationshipType) { + this.relationshipType = relationshipType; + } + + public UUID getRightId() { + return rightId; + } + + public void setRightId(UUID rightId) { + this.rightId = rightId; + } + + public int getLeftPlace() { + return leftPlace; + } + + public void setLeftPlace(int leftPlace) { + this.leftPlace = leftPlace; + } + + public int getRightPlace() { + return rightPlace; + } + + public void setRightPlace(int rightPlace) { + this.rightPlace = rightPlace; + } + + public int getRelationshipTypeId() { + return relationshipTypeId; + } + + public void setRelationshipTypeId(int relationshipTypeId) { + this.relationshipTypeId = relationshipTypeId; + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/RelationshipRestWrapper.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/RelationshipRestWrapper.java new file mode 100644 index 0000000000..52b87a8fbc --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/RelationshipRestWrapper.java @@ -0,0 +1,72 @@ +/** + * 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.List; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.dspace.app.rest.RelationshipRestController; + +/** + * This is the RestWrapper object for the RelationshipRestResource class. This will contain all the data that is + * used in that resource and more specifically, the label, dsoid and list of RelationshipRest objects + * The other methods are generic getters and setters + * This will be updated in https://jira.duraspace.org/browse/DS-4084 + */ +public class RelationshipRestWrapper implements RestAddressableModel { + public static final String NAME = "relationship"; + public static final String CATEGORY = RestAddressableModel.CORE; + + @JsonIgnore + private List relationshipRestList; + + private String label; + private String dsoId; + + + + public List getRelationshipRestList() { + return relationshipRestList; + } + + public void setRelationshipRestList(List relationshipRestList) { + this.relationshipRestList = relationshipRestList; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + public Class getController() { + return RelationshipRestController.class; + } + + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getType() { + return NAME; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getDsoId() { + return dsoId; + } + + public void setDsoId(String dsoId) { + this.dsoId = dsoId; + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/RelationshipTypeRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/RelationshipTypeRest.java new file mode 100644 index 0000000000..8f012247f1 --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/RelationshipTypeRest.java @@ -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.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.dspace.app.rest.RestResourceController; + +/** + * This class is the REST representation of the RelationshipType model class. + * This class acts as a data holder for the RelationshipTypeResource class + * Refer to {@link org.dspace.content.RelationshipType} for an explanation of the properties + */ +public class RelationshipTypeRest extends BaseObjectRest { + + public static final String NAME = "relationshiptype"; + public static final String CATEGORY = "core"; + + private String leftLabel; + private String rightLabel; + private Integer leftMinCardinality; + private Integer leftMaxCardinality; + private Integer rightMinCardinality; + private Integer rightMaxCardinality; + private EntityTypeRest leftType; + private EntityTypeRest rightType; + + public String getType() { + return NAME; + } + + public String getCategory() { + return CATEGORY; + } + + public Class getController() { + return RestResourceController.class; + } + + public String getLeftLabel() { + return leftLabel; + } + + public void setLeftLabel(String leftLabel) { + this.leftLabel = leftLabel; + } + + public String getRightLabel() { + return rightLabel; + } + + public void setRightLabel(String rightLabel) { + this.rightLabel = rightLabel; + } + + public Integer getLeftMinCardinality() { + return leftMinCardinality; + } + + public void setLeftMinCardinality(Integer leftMinCardinality) { + this.leftMinCardinality = leftMinCardinality; + } + + public Integer getLeftMaxCardinality() { + return leftMaxCardinality; + } + + public void setLeftMaxCardinality(Integer leftMaxCardinality) { + this.leftMaxCardinality = leftMaxCardinality; + } + + public Integer getRightMinCardinality() { + return rightMinCardinality; + } + + public void setRightMinCardinality(Integer rightMinCardinality) { + this.rightMinCardinality = rightMinCardinality; + } + + public Integer getRightMaxCardinality() { + return rightMaxCardinality; + } + + public void setRightMaxCardinality(Integer rightMaxCardinality) { + this.rightMaxCardinality = rightMaxCardinality; + } + + @LinkRest(linkClass = EntityTypeRest.class) + @JsonIgnore + public EntityTypeRest getLeftType() { + return leftType; + } + + public void setLeftType(EntityTypeRest leftType) { + this.leftType = leftType; + } + + @LinkRest(linkClass = EntityTypeRest.class) + @JsonIgnore + public EntityTypeRest getRightType() { + return rightType; + } + + public void setRightType(EntityTypeRest rightType) { + this.rightType = rightType; + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/RelationshipTypeRestWrapper.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/RelationshipTypeRestWrapper.java new file mode 100644 index 0000000000..4a3ffbb6ab --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/RelationshipTypeRestWrapper.java @@ -0,0 +1,67 @@ +/** + * 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.List; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.dspace.app.rest.RelationshipTypeRestController; + +/** + * This is the RestWrapper object for the RelationshipTypeRestResource class. This will contain all the data that is + * used in that resource and more specifically, the entityTypeLabel, entityTypeId and list of + * RelationshipTypeRest objects + * The other methods are generic getters and setters + */ +public class RelationshipTypeRestWrapper implements RestAddressableModel { + + @JsonIgnore + private List relationshipTypeRestList; + + private String entityTypeLabel; + private Integer entityTypeId; + + + + public List getRelationshipTypeRestList() { + return relationshipTypeRestList; + } + + public void setRelationshipTypeRestList( + List relationshipTypeRestList) { + this.relationshipTypeRestList = relationshipTypeRestList; + } + + public String getCategory() { + return "core"; + } + + public Class getController() { + return RelationshipTypeRestController.class; + } + + public String getType() { + return "relationshiptype"; + } + + public String getEntityTypeLabel() { + return entityTypeLabel; + } + + public void setEntityTypeLabel(String label) { + this.entityTypeLabel = label; + } + + public Integer getEntityTypeId() { + return entityTypeId; + } + + public void setEntityTypeId(Integer entityTypeId) { + this.entityTypeId = entityTypeId; + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/DSpaceResource.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/DSpaceResource.java index dda7e76922..ef06f1360f 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/DSpaceResource.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/DSpaceResource.java @@ -28,6 +28,7 @@ import org.dspace.app.rest.utils.Utils; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; import org.springframework.hateoas.Link; /** @@ -117,14 +118,16 @@ public abstract class DSpaceResource extends HAL DSpaceRestRepository resourceRepository = utils .getResourceRepository(linkedRMList.get(0).getCategory(), linkedRMList.get(0).getType()); - // TODO should we force pagination also of embedded resource? - // This will force pagination with size 10 for embedded collections as well -// int pageSize = 1; -// PageImpl page = new PageImpl( -// linkedRMList.subList(0, -// linkedRMList.size() > pageSize ? pageSize : linkedRMList.size()), -// new PageRequest(0, pageSize), linkedRMList.size()); - PageImpl page = new PageImpl(linkedRMList); + // force pagination also of embedded resource + // This will force pagination with size 20 for embedded collections as well + int pageSize = 20; + PageImpl page = new PageImpl( + linkedRMList.subList(0, + linkedRMList + .size() > pageSize ? pageSize : linkedRMList + .size()), + new PageRequest(0, pageSize), linkedRMList.size()); +// PageImpl page = new PageImpl(linkedRMList); wrapObject = new EmbeddedPage(linkToSubResource.getHref(), page.map(resourceRepository::wrapResource), linkedRMList, name); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/EntityTypeResource.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/EntityTypeResource.java new file mode 100644 index 0000000000..9dae4eee15 --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/EntityTypeResource.java @@ -0,0 +1,23 @@ +/** + * 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.EntityTypeRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * EntityType HAL Resource. This resource adds the data from the REST object together with embedded objects + * and a set of links if applicable + */ +@RelNameDSpaceResource(EntityTypeRest.NAME) +public class EntityTypeResource extends DSpaceResource { + public EntityTypeResource(EntityTypeRest data, Utils utils, String... rels) { + super(data, utils, rels); + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/FilteredDiscoveryPageResource.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/FilteredDiscoveryPageResource.java new file mode 100644 index 0000000000..356d0342cc --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/FilteredDiscoveryPageResource.java @@ -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.FilteredDiscoveryPageRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * FilteredDiscoveryPage HAL Resource. This resource adds the data from the REST object together with embedded objects + * and a set of links if applicable + */ +@RelNameDSpaceResource(FilteredDiscoveryPageRest.NAME) +public class FilteredDiscoveryPageResource extends DSpaceResource { + public FilteredDiscoveryPageResource(FilteredDiscoveryPageRest data, Utils utils, + String... rels) { + super(data, utils, rels); + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipResource.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipResource.java new file mode 100644 index 0000000000..8bebdac35d --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipResource.java @@ -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.RelationshipRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * Relationship HAL Resource. This resource adds the data from the REST object together with embedded objects + * and a set of links if applicable + */ +@RelNameDSpaceResource(RelationshipRest.NAME) +public class RelationshipResource extends DSpaceResource { + public RelationshipResource(RelationshipRest data, Utils utils, String... rels) { + super(data, utils, rels); + + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipResourceWrapper.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipResourceWrapper.java new file mode 100644 index 0000000000..0594eeb52c --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipResourceWrapper.java @@ -0,0 +1,51 @@ +/** + * 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 java.util.LinkedList; +import java.util.List; + +import org.dspace.app.rest.model.RelationshipRest; +import org.dspace.app.rest.model.RelationshipRestWrapper; +import org.dspace.app.rest.utils.Utils; +import org.springframework.data.domain.Pageable; + +/** + * This is the RelationshipResourceWrapper class which will take the RelationshipRestWrapper's data and transform + * this into a resource with the data, embeds and links. + */ +public class RelationshipResourceWrapper extends HALResource { + + /** + * The constructor for the RelationshipResourceWrapper + * This will call the HALResource constructor and additionally add embeds to the resource + * @param content The RelationshipRestWrapper object that contains the data + * @param utils The Util object + * @param totalElements The total amount of elements to be included in the list + * @param pageable The pageable object + */ + public RelationshipResourceWrapper(RelationshipRestWrapper content, Utils utils, Integer totalElements, + Pageable pageable) { + super(content); + addEmbeds(content, utils, pageable); + } + + private void addEmbeds(RelationshipRestWrapper content, Utils utils, + Pageable pageable) { + List list = new LinkedList<>(); + for (RelationshipRest relationshipRest : content.getRelationshipRestList()) { + list.add(new RelationshipResource(relationshipRest, utils)); + } + int begin = pageable.getOffset(); + int end = (pageable.getOffset() + pageable.getPageSize()) > list.size() ? list.size() : + pageable.getOffset() + pageable.getPageSize(); + list = list.subList(begin, end); + embedResource("relationships", list); + } + +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipTypeResource.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipTypeResource.java new file mode 100644 index 0000000000..8230eab549 --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipTypeResource.java @@ -0,0 +1,23 @@ +/** + * 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.RelationshipTypeRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * RelationshipType HAL Resource. This resource adds the data from the REST object together with embedded objects + * and a set of links if applicable + */ +@RelNameDSpaceResource(RelationshipTypeRest.NAME) +public class RelationshipTypeResource extends DSpaceResource { + public RelationshipTypeResource(RelationshipTypeRest data, Utils utils, String... rels) { + super(data, utils, rels); + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipTypeResourceWrapper.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipTypeResourceWrapper.java new file mode 100644 index 0000000000..6045946634 --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipTypeResourceWrapper.java @@ -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.app.rest.model.hateoas; + +import java.util.LinkedList; +import java.util.List; + +import org.dspace.app.rest.model.RelationshipTypeRest; +import org.dspace.app.rest.model.RelationshipTypeRestWrapper; +import org.dspace.app.rest.utils.Utils; + +/** + * This is the RelationshipTypeResourceWrapper class which will take the + * RelationshipTypeRestWrapper's data and transform this into a resource with the data, embeds and links. + */ +public class RelationshipTypeResourceWrapper extends HALResource { + + /** + * The constructor for the RelationshipTypeResourceWrapper + * This will call the HALResource constructor and additionally add embeds to the resource + * @param content The RelationshipTypeRestWrapper object that contains the data + * @param utils The Util object + */ + public RelationshipTypeResourceWrapper(RelationshipTypeRestWrapper content, Utils utils) { + super(content); + addEmbeds(content, utils); + } + + private void addEmbeds(RelationshipTypeRestWrapper content, Utils utils) { + List list = new LinkedList<>(); + for (RelationshipTypeRest relationshipTypeRest : content.getRelationshipTypeRestList()) { + list.add(new RelationshipTypeResource(relationshipTypeRest, utils)); + } + + embedResource("relationshiptypes", list); + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java index 986aadb1b1..93f2adbfc6 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java @@ -11,6 +11,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.Serializable; import java.sql.SQLException; +import java.util.List; import javax.servlet.http.HttpServletRequest; @@ -258,6 +259,27 @@ public abstract class DSpaceRestRepository wrapResource(T model, String... rels); + /** + * Create and return a new instance. Data is recovered from the thread bound HTTP request and the list + * of DSpaceObjects provided in the uri-list body + * + * @param list The list of Strings to be used in the createAndReturn method + * @return The created REST object + */ + public T createAndReturn(List list) { + Context context = null; + try { + context = obtainContext(); + T entity = thisRepository.createAndReturn(context, list); + context.commit(); + return entity; + } catch (AuthorizeException e) { + throw new RESTAuthorizationException(e); + } catch (SQLException ex) { + throw new RuntimeException(ex.getMessage(), ex); + } + } + /** * Create and return a new instance. Data are usually retrieved from the thread bound http request * @@ -277,6 +299,26 @@ public abstract class DSpaceRestRepository list) + throws AuthorizeException, SQLException, RepositoryMethodNotImplementedException { + throw new RepositoryMethodNotImplementedException("No implementation found; Method not allowed!", ""); + } + /** * Method to implement to support the creation of a new instance. Usually require to retrieve the http request from * the thread bound attribute @@ -290,7 +332,7 @@ public abstract class DSpaceRestRepository stringList) { + Context context = obtainContext(); + try { + thisRepository.put(context, request, apiCategory, model, id, stringList); + context.commit(); + } catch (SQLException | AuthorizeException e) { + throw new RuntimeException(e.getMessage(), e); + } + return findOne(id); + } + + /** + * Implement this method in the subclass to support updating a REST object. * * @param context the dspace context - * @param request the http request * @param apiCategory the API category e.g. "api" * @param model the DSpace model e.g. "metadatafield" * @param id the ID of the target REST object @@ -469,8 +530,29 @@ public abstract class DSpaceRestRepository stringList) + throws RepositoryMethodNotImplementedException, SQLException, AuthorizeException { + throw new RepositoryMethodNotImplementedException(apiCategory, model); + } + } \ No newline at end of file diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/EntityTypeRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/EntityTypeRestRepository.java new file mode 100644 index 0000000000..31c4e4392c --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/EntityTypeRestRepository.java @@ -0,0 +1,68 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.app.rest.converter.EntityTypeConverter; +import org.dspace.app.rest.model.EntityTypeRest; +import org.dspace.app.rest.model.hateoas.DSpaceResource; +import org.dspace.app.rest.model.hateoas.EntityTypeResource; +import org.dspace.content.EntityType; +import org.dspace.content.service.EntityTypeService; +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.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.stereotype.Component; + +/** + * This is the repository that is responsible to manage EntityType Rest objects + */ +@Component(EntityTypeRest.CATEGORY + "." + EntityTypeRest.NAME) +public class EntityTypeRestRepository extends DSpaceRestRepository { + + @Autowired + private EntityTypeService entityTypeService; + + @Autowired + private EntityTypeConverter entityTypeConverter; + + public EntityTypeRest findOne(Context context, Integer integer) { + try { + EntityType entityType = entityTypeService.find(context, integer); + if (entityType == null) { + throw new ResourceNotFoundException("The entityType for ID: " + integer + " could not be found"); + } + return entityTypeConverter.fromModel(entityType); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + public Page findAll(Context context, Pageable pageable) { + List entityTypeList = null; + try { + entityTypeList = entityTypeService.findAll(context); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + Page page = utils.getPage(entityTypeList, pageable).map(entityTypeConverter); + return page; + } + + public Class getDomainClass() { + return EntityTypeRest.class; + } + + public DSpaceResource wrapResource(EntityTypeRest model, String... rels) { + return new EntityTypeResource(model, utils, rels); + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/FilteredDiscoveryPageRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/FilteredDiscoveryPageRestRepository.java new file mode 100644 index 0000000000..a5e96ae809 --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/FilteredDiscoveryPageRestRepository.java @@ -0,0 +1,74 @@ +/** + * 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.LinkedList; +import java.util.List; + +import org.dspace.app.rest.converter.FilteredDiscoveryPageConverter; +import org.dspace.app.rest.model.FilteredDiscoveryPageRest; +import org.dspace.app.rest.model.hateoas.DSpaceResource; +import org.dspace.app.rest.model.hateoas.FilteredDiscoveryPageResource; +import org.dspace.content.EntityType; +import org.dspace.content.service.EntityTypeService; +import org.dspace.content.virtual.EntityTypeToFilterQueryService; +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.stereotype.Component; + +/** + * This is the repository that is responsible to manage FilteredDiscoveryPage Rest objects + */ +@Component(FilteredDiscoveryPageRest.CATEGORY + "." + FilteredDiscoveryPageRest.NAME) +public class FilteredDiscoveryPageRestRepository extends DSpaceRestRepository { + + @Autowired + private EntityTypeService entityTypeService; + + @Autowired + private FilteredDiscoveryPageConverter filteredDiscoveryPageConverter; + + @Autowired + private EntityTypeToFilterQueryService entityTypeToFilterQueryService; + + public FilteredDiscoveryPageRest findOne(Context context, String string) { + try { + return filteredDiscoveryPageConverter.fromModel(entityTypeService.findByEntityType(context, string)); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + public Page findAll(Context context, Pageable pageable) { + List entityTypeList = null; + try { + entityTypeList = entityTypeService.findAll(context); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + List resultingList = new LinkedList<>(); + for (EntityType entityType : entityTypeList) { + if (entityTypeToFilterQueryService.hasKey(entityType.getLabel())) { + resultingList.add(entityType); + } + } + Page page = utils.getPage(resultingList, pageable) + .map(filteredDiscoveryPageConverter); + return page; } + + public Class getDomainClass() { + return FilteredDiscoveryPageRest.class; + } + + public DSpaceResource wrapResource(FilteredDiscoveryPageRest model, String... rels) { + return new FilteredDiscoveryPageResource(model, utils, rels); + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java new file mode 100644 index 0000000000..378fea6418 --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java @@ -0,0 +1,201 @@ +/** + * 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.List; +import javax.servlet.http.HttpServletRequest; + +import org.apache.log4j.Logger; +import org.dspace.app.rest.converter.RelationshipConverter; +import org.dspace.app.rest.converter.RelationshipTypeConverter; +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.RelationshipRest; +import org.dspace.app.rest.model.hateoas.DSpaceResource; +import org.dspace.app.rest.model.hateoas.RelationshipResource; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.content.Relationship; +import org.dspace.content.RelationshipType; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.RelationshipService; +import org.dspace.content.service.RelationshipTypeService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.stereotype.Component; + +/** + * This is the repository that is responsible to manage Relationship Rest objects + */ +@Component(RelationshipRest.CATEGORY + "." + RelationshipRest.NAME) +public class RelationshipRestRepository extends DSpaceRestRepository { + + private static final Logger log = Logger.getLogger(RelationshipRestRepository.class); + + + @Autowired + private RelationshipService relationshipService; + + @Autowired + private RelationshipConverter relationshipConverter; + + @Autowired + private RelationshipTypeConverter relationshipTypeConverter; + + @Autowired + private RelationshipTypeService relationshipTypeService; + + @Autowired + private AuthorizeService authorizeService; + + @Autowired + private ItemService itemService; + + public RelationshipRest findOne(Context context, Integer integer) { + try { + return relationshipConverter.fromModel(relationshipService.find(context, integer)); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + public Page findAll(Context context, Pageable pageable) { + List relationships = null; + try { + relationships = relationshipService.findAll(context); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + Page page = utils.getPage(relationships, pageable).map(relationshipConverter); + return page; + } + + public Class getDomainClass() { + return RelationshipRest.class; + } + + public DSpaceResource wrapResource(RelationshipRest model, String... rels) { + return new RelationshipResource(model, utils, rels); + } + + @Override + protected RelationshipRest createAndReturn(Context context, List stringList) + throws AuthorizeException, SQLException, RepositoryMethodNotImplementedException { + + HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); + List list = utils.constructDSpaceObjectList(context, stringList); + if (list.size() == 2 && list.get(0).getType() == Constants.ITEM && list.get(1).getType() == Constants.ITEM) { + Item leftItem = (Item) list.get(0); + Item rightItem = (Item) list.get(1); + RelationshipType relationshipType = relationshipTypeService + .find(context, Integer.parseInt(req.getParameter("relationshipType"))); + + EPerson ePerson = context.getCurrentUser(); + if (authorizeService.authorizeActionBoolean(context, leftItem, Constants.WRITE) || + authorizeService.authorizeActionBoolean(context, rightItem, Constants.WRITE)) { + Relationship relationship = relationshipService.create(context, leftItem, rightItem, + relationshipType, 0, 0); + // The above if check deals with the case that a Relationship can be created if the user has write + // rights on one of the two items. The following updateItem calls can however call the + // ItemService.update() functions which would fail if the user doesn't have permission on both items. + // Since we allow this creation to happen under these circumstances, we need to turn off the + // authorization system here so that this failure doesn't happen when the items need to be update + context.turnOffAuthorisationSystem(); + relationshipService.updateItem(context, relationship.getLeftItem()); + relationshipService.updateItem(context, relationship.getRightItem()); + context.restoreAuthSystemState(); + return relationshipConverter.fromModel(relationship); + } else { + throw new AccessDeniedException("You do not have write rights on this relationship's items"); + } + } else { + throw new UnprocessableEntityException("The given items in the request were not valid items"); + } + + + } + + /* + * Disabled the put until https://jira.duraspace.org/browse/DS-4230 is discussed + @Override + protected RelationshipRest put(Context context, HttpServletRequest request, String apiCategory, String model, + Integer id, List stringList) + throws RepositoryMethodNotImplementedException, SQLException, AuthorizeException { + + Relationship relationship = relationshipService.find(context, id); + if (relationship == null) { + throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + id + " not found"); + } + List dSpaceObjects = utils.constructDSpaceObjectList(context, stringList); + if (dSpaceObjects.size() == 2 && dSpaceObjects.get(0).getType() == Constants.ITEM + && dSpaceObjects.get(1).getType() == Constants.ITEM) { + Item leftItem = (Item) dSpaceObjects.get(0); + Item rightItem = (Item) dSpaceObjects.get(1); + + if (isAllowedToModifyRelationship(context, relationship, leftItem, rightItem)) { + relationship.setLeftItem(leftItem); + relationship.setRightItem(rightItem); + + relationshipService.updatePlaceInRelationship(context, relationship, false); + relationshipService.update(context, relationship); + + return relationshipConverter.fromModel(relationship); + } else { + throw new AccessDeniedException("You do not have write rights on this relationship's items"); + } + } else { + throw new UnprocessableEntityException("The given items in the request were not valid"); + } + + } + */ + + /** + * This method will check with the current user has write rights on both one of the original items and one of the + * new items for the relationship. + * @param context The relevant DSpace context + * @param relationship The relationship to be checked on + * @param leftItem The new left Item + * @param rightItem The new right Item + * @return A boolean indicating whether the user is allowed or not + * @throws SQLException If something goes wrong + */ + private boolean isAllowedToModifyRelationship(Context context, Relationship relationship, Item leftItem, + Item rightItem) throws SQLException { + return (authorizeService.authorizeActionBoolean(context, leftItem, Constants.WRITE) || + authorizeService.authorizeActionBoolean(context, rightItem, Constants.WRITE)) && + (authorizeService.authorizeActionBoolean(context, relationship.getLeftItem(), Constants.WRITE) || + authorizeService.authorizeActionBoolean(context, relationship.getRightItem(), Constants.WRITE) + ); + } + + @Override + protected void delete(Context context, Integer id) throws AuthorizeException { + Relationship relationship = null; + try { + relationship = relationshipService.find(context, id); + if (relationship != null) { + try { + relationshipService.delete(context, relationship); + } catch (AuthorizeException e) { + throw new AccessDeniedException("You do not have write rights on this relationship's items"); + } + } + } catch (SQLException e) { + log.error("Error deleting Relationship specified by ID:" + id, e); + } + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRestRepository.java new file mode 100644 index 0000000000..ff163555f6 --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRestRepository.java @@ -0,0 +1,63 @@ +/** + * 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.List; + +import org.dspace.app.rest.converter.RelationshipTypeConverter; +import org.dspace.app.rest.model.RelationshipTypeRest; +import org.dspace.app.rest.model.hateoas.DSpaceResource; +import org.dspace.app.rest.model.hateoas.RelationshipTypeResource; +import org.dspace.content.RelationshipType; +import org.dspace.content.service.RelationshipTypeService; +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.stereotype.Component; + +/** + * This is the repository that is responsible to manage RelationshipType Rest objects + */ +@Component(RelationshipTypeRest.CATEGORY + "." + RelationshipTypeRest.NAME) +public class RelationshipTypeRestRepository extends DSpaceRestRepository { + + @Autowired + private RelationshipTypeService relationshipTypeService; + + @Autowired + private RelationshipTypeConverter relationshipTypeConverter; + + public RelationshipTypeRest findOne(Context context, Integer integer) { + try { + return relationshipTypeConverter.fromModel(relationshipTypeService.find(context, integer)); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + public Page findAll(Context context, Pageable pageable) { + List relationshipTypeList = null; + try { + relationshipTypeList = relationshipTypeService.findAll(context); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + Page page = utils.getPage(relationshipTypeList, pageable).map(relationshipTypeConverter); + return page; + } + + public Class getDomainClass() { + return RelationshipTypeRest.class; + } + + public DSpaceResource wrapResource(RelationshipTypeRest model, String... rels) { + return new RelationshipTypeResource(model, utils, rels); + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/WorkflowItemRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/WorkflowItemRestRepository.java index b847338df6..306294a73a 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/WorkflowItemRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/WorkflowItemRestRepository.java @@ -144,10 +144,13 @@ public class WorkflowItemRestRepository extends DSpaceRestRepository stringList) { XmlWorkflowItem source; + if (stringList == null || stringList.isEmpty() || stringList.size() > 1) { + throw new UnprocessableEntityException("The given URI list could not be properly parsed to one result"); + } try { - source = submissionService.createWorkflowItem(context, getRequestService().getCurrentRequest()); + source = submissionService.createWorkflowItem(context, stringList.get(0)); } catch (AuthorizeException e) { throw new RESTAuthorizationException(e); } catch (WorkflowException e) { diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java index 259ece7d4a..16aef3ffcd 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/security/WebSecurityConfiguration.java @@ -84,23 +84,24 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { //Logout configuration .logout() - //On logout, clear the "session" salt - .addLogoutHandler(customLogoutHandler) - //Configure the logout entry point - .logoutRequestMatcher(new AntPathRequestMatcher("/api/authn/logout")) - //When logout is successful, return OK (204) status - .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.NO_CONTENT)) - //Everyone can call this endpoint - .permitAll() + //On logout, clear the "session" salt + .addLogoutHandler(customLogoutHandler) + //Configure the logout entry point + .logoutRequestMatcher(new AntPathRequestMatcher("/api/authn/logout")) + //When logout is successful, return OK (204) status + .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.NO_CONTENT)) + //Everyone can call this endpoint + .permitAll() .and() //Configure the URL patterns with their authentication requirements - .authorizeRequests() - //Allow POST by anyone on the login endpoint - .antMatchers(HttpMethod.POST,"/api/authn/login").permitAll() - //TRACE, CONNECT, OPTIONS, HEAD - //Everyone can call GET on the status endpoint - .antMatchers(HttpMethod.GET, "/api/authn/status").permitAll() + //Enable Spring Security authorization on /api/ URLs only + .antMatcher("/api/**").authorizeRequests() + //Allow POST by anyone on the login endpoint + .antMatchers(HttpMethod.POST,"/api/authn/login").permitAll() + //TRACE, CONNECT, OPTIONS, HEAD + //Everyone can call GET on the status endpoint + .antMatchers(HttpMethod.GET, "/api/authn/status").permitAll() .and() //Add a filter before our login endpoints to do the authentication based on the data in the HTTP request diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/submit/SubmissionService.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/submit/SubmissionService.java index c704007bf4..eecf0f5424 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/submit/SubmissionService.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/submit/SubmissionService.java @@ -8,7 +8,6 @@ package org.dspace.app.rest.submit; import java.io.IOException; -import java.io.Reader; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; @@ -196,63 +195,48 @@ public class SubmissionService { * * @param context * the dspace context - * @param currentRequest - * the request containing the details about the workspace to create + * @param requestUriListString + * the id of the workspaceItem * @return * @throws SQLException * @throws AuthorizeException * @throws WorkflowException */ - public XmlWorkflowItem createWorkflowItem(Context context, Request currentRequest) + public XmlWorkflowItem createWorkflowItem(Context context, String requestUriListString) throws SQLException, AuthorizeException, WorkflowException { - Reader reader = null; XmlWorkflowItem wi = null; - try { - //FIXME use utility method to extract the ID from the text/uri-list body - reader = currentRequest.getHttpServletRequest().getReader(); - char[] arr = new char[1024]; - StringBuilder buffer = new StringBuilder(); - int numCharsRead = reader.read(arr, 0, arr.length); - if (numCharsRead > 0) { - buffer.append(arr, 0, numCharsRead); - } - if (numCharsRead == arr.length) { - throw new UnprocessableEntityException("Malformed body... too long"); - } - String regex = "\\/api\\/" + WorkspaceItemRest.CATEGORY + "\\/" + English.plural(WorkspaceItemRest.NAME) - + "\\/"; - String[] split = buffer.toString().split(regex, 2); - if (split.length != 2) { - throw new UnprocessableEntityException("Malformed body..." + buffer); - } - // END FIXME - WorkspaceItem wsi = null; - try { - wsi = workspaceItemService.find(context, Integer.parseInt(split[1])); - } catch (NumberFormatException e) { - throw new UnprocessableEntityException("The provided workspaceitem URI is not valid"); - } - if (wsi == null) { - throw new UnprocessableEntityException("Workspace item is not found"); - } - if (!workspaceItemConverter.convert(wsi).getErrors().isEmpty()) { - throw new UnprocessableEntityException( - "Start workflow failed due to validation error on workspaceitem"); - } - - wi = workflowService.start(context, wsi); - - } catch (IOException e) { - throw new RuntimeException(e.getMessage(), e); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - throw new RuntimeException(e.getMessage(), e); - } - } + if (StringUtils.isBlank(requestUriListString)) { + throw new UnprocessableEntityException("Malformed body..." + requestUriListString); } + String regex = "\\/api\\/" + WorkspaceItemRest.CATEGORY + "\\/" + English.plural(WorkspaceItemRest.NAME) + + "\\/"; + String[] split = requestUriListString.split(regex, 2); + if (split.length != 2) { + throw new UnprocessableEntityException("Malformed body..." + requestUriListString); + } + WorkspaceItem wsi = null; + int id = 0; + try { + id = Integer.parseInt(split[1]); + wsi = workspaceItemService.find(context, id); + } catch (NumberFormatException e) { + throw new UnprocessableEntityException("The provided workspaceitem URI is not valid"); + } + if (wsi == null) { + throw new UnprocessableEntityException("Workspace item is not found"); + } + if (!workspaceItemConverter.convert(wsi).getErrors().isEmpty()) { + throw new UnprocessableEntityException( + "Start workflow failed due to validation error on workspaceitem"); + } + + try { + wi = workflowService.start(context, wsi); + } catch (IOException e) { + throw new RuntimeException("The workflow could not be started for workspaceItem with" + + "id: " + id); + } + return wi; } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java index 71651fa133..fd9059faa6 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java @@ -13,24 +13,24 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.web.config.EnableSpringDataWebSupport; /** - * This class provide extra configuration for our Spring Boot Application + * This class provides extra configuration for our Spring Boot Application + *

+ * NOTE: @ComponentScan on "org.dspace.app.configuration" provides a way for other DSpace modules or plugins + * to "inject" their own Spring configurations / subpaths into our Spring Boot webapp. * * @author Andrea Bollini (andrea.bollini at 4science.it) + * @author Tim Donohue */ @Configuration @EnableSpringDataWebSupport -@ComponentScan( {"org.dspace.app.rest.converter", "org.dspace.app.rest.repository", "org.dspace.app.rest.utils"}) +@ComponentScan( {"org.dspace.app.rest.converter", "org.dspace.app.rest.repository", "org.dspace.app.rest.utils", + "org.dspace.app.configuration"}) public class ApplicationConfig { - @Value("${dspace.dir}") - private String dspaceHome; - - @Value("${cors.allowed-origins}") + // Allowed CORS origins. Defaults to * (everywhere) + // Can be overridden in DSpace configuration + @Value("${rest.cors.allowed-origins:*}") private String corsAllowedOrigins; - public String getDspaceHome() { - return dspaceHome; - } - public String[] getCorsAllowedOrigins() { if (corsAllowedOrigins != null) { return corsAllowedOrigins.split("\\s*,\\s*"); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceConfigurationInitializer.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceConfigurationInitializer.java new file mode 100644 index 0000000000..2e05ea3fb7 --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceConfigurationInitializer.java @@ -0,0 +1,48 @@ +/** + * 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.utils; + +import org.apache.commons.configuration2.Configuration; +import org.apache.commons.configuration2.spring.ConfigurationPropertySource; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * Utility class that will initialize the DSpace Configuration on Spring Boot startup. + *

+ * NOTE: MUST be loaded after DSpaceKernelInitializer, as it requires the kernel is already initialized. + *

+ * This initializer ensures that our DSpace Configuration is loaded into Spring's list of PropertySources + * very early in the Spring Boot startup process. That is important as it allows us to use DSpace configurations + * within @ConditionalOnProperty annotations on beans, as well as @Value annotations and XML bean definitions. + *

+ * Used by org.dspace.app.rest.Application + */ +public class DSpaceConfigurationInitializer implements ApplicationContextInitializer { + + private static final Logger log = LoggerFactory.getLogger(DSpaceConfigurationInitializer.class); + + @Override + public void initialize(final ConfigurableApplicationContext applicationContext) { + // Load DSpace Configuration service (requires kernel already initialized) + ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + Configuration configuration = configurationService.getConfiguration(); + + // Create an Apache Commons Configuration Property Source from our configuration + ConfigurationPropertySource apacheCommonsConfigPropertySource = + new ConfigurationPropertySource(configuration.getClass().getName(), configuration); + + // Append it to the Environment's list of PropertySources + applicationContext.getEnvironment().getPropertySources().addLast(apacheCommonsConfigPropertySource); + } +} + diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceKernelInitializer.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceKernelInitializer.java index 039cfa5af9..73a96259bf 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceKernelInitializer.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceKernelInitializer.java @@ -11,6 +11,7 @@ import java.io.File; import javax.naming.Context; import javax.naming.InitialContext; +import org.apache.commons.lang3.StringUtils; import org.dspace.kernel.DSpaceKernel; import org.dspace.kernel.DSpaceKernelManager; import org.dspace.servicemanager.DSpaceKernelImpl; @@ -22,6 +23,7 @@ import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.event.ContextClosedEvent; +import org.springframework.core.env.ConfigurableEnvironment; /** * Utility class that will initialize the DSpace Kernel on Spring Boot startup. @@ -35,16 +37,16 @@ public class DSpaceKernelInitializer implements ApplicationContextInitializer> dSpaceObjectServices; + + public Page getPage(List fullContents, Pageable pageable) { int total = fullContents.size(); List pageContent = null; @@ -219,4 +236,82 @@ public class Utils { return multipartFile.getName(); } } + + /** + * This method will construct a List of DSpaceObjects by executing the method + * {@link Utils#readFromRequest(HttpServletRequest)} and fetching the List of Strings from the request. + * The method will iterate over this list of Strings and parse the String to retrieve the UUID from it. + * It will then look through all the DSpaceObjectServices to try and match this UUID to a DSpaceObject. + * If one is found, this DSpaceObject is added to the List of DSpaceObjects that we will return. + * @param context The relevant DSpace context + * @param request The request out of which we'll create the List of DSpaceObjects + * @return The resulting list of DSpaceObjects that we parsed out of the request + */ + public List constructDSpaceObjectList(Context context, List list) { + List dSpaceObjects = new LinkedList<>(); + for (String string : list) { + if (string.endsWith("/")) { + string = string.substring(0, string.length() - 1); + } + String uuid = string.substring(string.lastIndexOf('/') + 1); + try { + for (DSpaceObjectService dSpaceObjectService : dSpaceObjectServices) { + DSpaceObject dSpaceObject = dSpaceObjectService.find(context, UUIDUtils.fromString(uuid)); + if (dSpaceObject != null) { + dSpaceObjects.add(dSpaceObject); + break; + } + } + } catch (SQLException e) { + log.error("Could not find DSpaceObject for UUID: " + uuid, e); + } + + } + return dSpaceObjects; + } + + /** + * This method reads lines from the request's InputStream and will add this to a list of Strings. + * @param request The request from which the InputStream will be fetched + * @return A list of String constructed from the request's InputStream + * @throws IOException If something goes wrong + */ + private List readFromRequest(HttpServletRequest request) throws IOException { + List list = new LinkedList<>(); + Scanner scanner = new Scanner(request.getInputStream()); + + try { + + while (scanner.hasNextLine()) { + + String line = scanner.nextLine(); + if (org.springframework.util.StringUtils.hasText(line)) { + list.add(line); + } + } + + } finally { + scanner.close(); + } + return list; + } + + + /** + * This method will retrieve a list of DSpaceObjects from the Request by reading in the Request's InputStream + * with a Scanner and searching the InputStream for UUIDs which will then be resolved to a DSpaceObject. + * These will all be added to a list and returned by this method. + * @param request The request of which the InputStream will be used + * @return The list of DSpaceObjects that we could find in the InputStream + * @throws IOException If something goes wrong + */ + public List getStringListFromRequest(HttpServletRequest request) { + List list = null; + try { + list = readFromRequest(request); + } catch (IOException e) { + log.error("Something went wrong with reading in the inputstream from the request", e); + } + return list; + } } diff --git a/dspace-spring-rest/src/main/resources/application.properties b/dspace-spring-rest/src/main/resources/application.properties index c6fbf3b6b1..7a5e5a0193 100644 --- a/dspace-spring-rest/src/main/resources/application.properties +++ b/dspace-spring-rest/src/main/resources/application.properties @@ -7,34 +7,31 @@ # # -# Spring Boot application.properties -# Docs (including info on how to override these default settings) +# Spring Boot's application.properties +# +# This properties file is used by Spring Boot to initialize its ApplicationContext and configure +# default Spring beans. It also uses the "dspace.dir" custom setting to locate your DSpace installation, +# and load all DSpace services and configurations. +# +# WARNING: Because this properties file initializes Spring Boot, it loads *before* any DSpace specific +# configurations/settings. Therefore settings in this file CANNOT depend on any DSpace configurations. +# The *only* DSpace configuration allowed in this file is "dspace.dir", which is documented below. +# +# Docs (including info on how to override these default settings) # http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html -# For common settings see: +# For common settings see: # http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html # -# -# TODO: Eventually would could think of "wiring" this up to use Commons Configuration as well -# See, for example: http://stackoverflow.com/questions/25271537/remote-propertysource -# and https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html ######################## # DSpace Settings # -# DSpace installation directory +# DSpace home/installation directory +# REQUIRED to be specified in this application.properties file, as it is used to locate and initialize +# the DSpace Kernel and all Services (including configuration). See org.dspace.app.rest.Application.getDSpaceHome() +# NOTE: this configuration is filled out by Apache Ant during the DSpace install/update process. It does NOT +# interact with or read its configuration from dspace.cfg. dspace.dir=${dspace.dir} -#dspace.dir=d:/install/dspace7 - -######################## -# DSpace API CORS Settings -# -cors.allowed-origins = * - -######################## -# Spring Boot Settings -# -# Testing an "application Name" -spring.application.name = DSpace Spring Rest ######################## # Spring DATA Rest settings @@ -75,15 +72,6 @@ server.port=8080 # (Optional, defaults to root context) #server.context-path=/spring-data-rest -# This creates a Tomcat context-param named "dspace.dir" -# and sets it to the value of the "dspace.dir" property (listed above) -server.context-parameters.dspace.dir=${dspace.dir} - -# This creates a Tomcat context-param named "dspace-config" -# (Used by DSpaceContextListener to load the configurations) -# This is only needed in DSpace 5 or below to initialize ConfigurationManager -#server.context-parameters.dspace-config=${dspace.dir}/config/dspace.cfg - # Error handling settings # Always include the fullstacktrace in error pages # Can be set to "never" if you don't want it. @@ -94,8 +82,8 @@ server.error.include-stacktrace = always # # DISABLE a few autoconfiguration scripts, as DSpace initializes/configures these already # * DataSourceAutoConfiguration (DB connection / datasource) +# * HibernateJpaAutoConfiguration (Hibernate ORM) # * FlywayAutoConfiguration (Flyway migrations) -# * HibernateJpaAutoConfiguration (Hibernate) # * SolrAutoConfiguration (Solr) # # TODO: At some point we may want to investigate whether we can re-enable these and remove the custom DSpace init code diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/oai/OAIpmhIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/oai/OAIpmhIT.java new file mode 100644 index 0000000000..3975d072ce --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/oai/OAIpmhIT.java @@ -0,0 +1,281 @@ +/** + * 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.oai; + +import static org.hamcrest.Matchers.startsWith; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath; + +import java.util.Date; + +import com.lyncode.xoai.dataprovider.core.XOAIManager; +import com.lyncode.xoai.dataprovider.exceptions.ConfigurationException; +import com.lyncode.xoai.dataprovider.services.api.ResourceResolver; +import com.lyncode.xoai.dataprovider.services.impl.BaseDateProvider; +import com.lyncode.xoai.dataprovider.xml.xoaiconfig.Configuration; +import com.lyncode.xoai.dataprovider.xml.xoaiconfig.ContextConfiguration; + +import org.dspace.app.rest.builder.CollectionBuilder; +import org.dspace.app.rest.builder.CommunityBuilder; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.content.Community; +import org.dspace.services.ConfigurationService; +import org.dspace.xoai.services.api.EarliestDateResolver; +import org.dspace.xoai.services.api.cache.XOAICacheService; +import org.dspace.xoai.services.api.config.XOAIManagerResolver; +import org.dspace.xoai.services.api.xoai.DSpaceFilterResolver; + +import org.junit.Assume; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.test.context.TestPropertySource; + +/** + * Integration test to verify the /oai endpoint is responding as a valid OAI-PMH endpoint. + * This tests that our dspace-oai module is running at this endpoint. + *

+ * This is an AbstractControllerIntegrationTest because dspace-oai makes use of Controllers. + * + * @author Tim Donohue + */ +// Ensure the OAI SERVER IS ENABLED before any tests run. +// This annotation overrides default DSpace config settings loaded into Spring Context +@TestPropertySource(properties = {"oai.enabled = true"}) +public class OAIpmhIT extends AbstractControllerIntegrationTest { + + @Autowired + private ConfigurationService configurationService; + + // All OAI-PMH paths that we test against + private final String ROOT_PATH = "/oai"; + private final String DEFAULT_CONTEXT_PATH = "request"; + private final String DEFAULT_CONTEXT = ROOT_PATH + "/" + DEFAULT_CONTEXT_PATH; + + // Mock to ensure XOAI caching is disabled for all tests (see @Before method) + @MockBean + private XOAICacheService xoaiCacheService; + + // Spy on the current EarliestDateResolver bean, to allow us to change behavior in tests below + @SpyBean + private EarliestDateResolver earliestDateResolver; + + // XOAI's BaseDateProvider (used for date-based testing below) + private static BaseDateProvider baseDateProvider = new BaseDateProvider(); + + // Spy on the current XOAIManagerResolver bean, to allow us to change behavior of XOAIManager in tests + // See also: createMockXOAIManager() method + @SpyBean + private XOAIManagerResolver xoaiManagerResolver; + + // Beans required by createMockXOAIManager() + @Autowired + private ResourceResolver resourceResolver; + @Autowired + private DSpaceFilterResolver filterResolver; + + + @Before + public void onlyRunIfConfigExists() { + // These integration tests REQUIRE that OAIWebConfig is found/available (as this class deploys OAI) + // If this class is not available, the below "Assume" will cause all tests to be SKIPPED + // NOTE: OAIWebConfig is provided by the 'dspace-oai' module + try { + Class.forName("org.dspace.app.configuration.OAIWebConfig"); + } catch (ClassNotFoundException ce) { + Assume.assumeNoException(ce); + } + + // Disable XOAI Caching for ALL tests + when(xoaiCacheService.isActive()).thenReturn(false); + when(xoaiCacheService.hasCache(anyString())).thenReturn(false); + } + + @Test + public void requestToRootShouldGiveListOfContextsWithBadRequestError() throws Exception { + // Attempt to call the root endpoint + getClient().perform(get(ROOT_PATH)) + // Expect a 400 response code (OAI requires a context) + .andExpect(status().isBadRequest()) + // Expect that a list of valid contexts is returned + .andExpect(model().attributeExists("contexts")) + ; + } + + @Test + public void requestForUnknownContextShouldGiveListOfContextsWithBadRequestError() throws Exception { + // Attempt to call an nonexistent OAI-PMH context + getClient().perform(get(ROOT_PATH + "/nonexistentContext")) + // Expect a 400 response code (OAI requires a context) + .andExpect(status().isBadRequest()) + // Expect that a list of valid contexts is returned + .andExpect(model().attributeExists("contexts")) + ; + } + + @Test + public void requestForIdentifyWithoutRequiredConfigShouldFail() throws Exception { + // Clear out the required "mail.admin" configuration + configurationService.setProperty("mail.admin", null); + + // Attempt to make an Identify request to root context + getClient().perform(get(DEFAULT_CONTEXT).param("verb", "Identify")) + // Expect a 500 response code (mail.admin MUST be set) + .andExpect(status().isInternalServerError()) + ; + } + + @Test + public void requestForIdentifyShouldReturnTheConfiguredValues() throws Exception { + + // Get current date/time and store as "now" + Date now = new Date(); + // Return "now" when "getEarliestDate()" is called for the currently loaded EarliestDateResolver bean + doReturn(now).when(earliestDateResolver).getEarliestDate(context); + + // Attempt to make an Identify request to root context + getClient().perform(get(DEFAULT_CONTEXT).param("verb", "Identify")) + // Expect a 200 response code + .andExpect(status().isOk()) + // Expect the content type to be "text/xml" + .andExpect(content().contentType("text/xml")) + // Expect oai + .andExpect(xpath("OAI-PMH/Identify/description/oai-identifier/scheme").string("oai")) + // Expect protocol version 2.0 + .andExpect(xpath("OAI-PMH/Identify/protocolVersion").string("2.0")) + // Expect repositoryName to be the same as "dspace.name" config + .andExpect(xpath("OAI-PMH/Identify/repositoryName") + .string(configurationService.getProperty("dspace.name"))) + // Expect adminEmail to be the same as "mail.admin" config + .andExpect(xpath("OAI-PMH/Identify/adminEmail") + .string(configurationService.getProperty("mail.admin"))) + // Expect baseURL to be the same as our "oai.url" with the DEFAULT_CONTEXT_PATH appended + .andExpect(xpath("OAI-PMH/Identify/baseURL") + .string(configurationService.getProperty("oai.url") + "/" + DEFAULT_CONTEXT_PATH)) + // Expect earliestDatestamp to be "now", i.e. current date, (as mocked above) + .andExpect(xpath("OAI-PMH/Identify/earliestDatestamp") + .string(baseDateProvider.format(now))) + ; + } + + @Test + public void listSetsWithLessSetsThenMaxSetsPerPage() throws Exception { + //Turn off the authorization system, otherwise we can't make the objects + context.turnOffAuthorisationSystem(); + + // Create a Community & a Collection + Community parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + CollectionBuilder.createCollection(context, parentCommunity) + .withName("Child Collection") + .build(); + context.restoreAuthSystemState(); + + // Call ListSets verb, and verify both Collection & Community are listed as sets + getClient().perform(get(DEFAULT_CONTEXT).param("verb", "ListSets")) + // Expect 200 response, with valid response date and verb=ListSets + .andExpect(status().isOk()) + .andExpect(xpath("OAI-PMH/responseDate").exists()) + .andExpect(xpath("OAI-PMH/request/@verb").string("ListSets")) + // Expect two Sets to be returned + .andExpect(xpath("//set").nodeCount(2)) + // First setSpec should start with "com_" (Community) + .andExpect(xpath("(//set/setSpec)[1]").string(startsWith("com_"))) + // First set name should be Community name + .andExpect(xpath("(//set/setName)[1]").string("Parent Community")) + // Second setSpec should start with "col_" (Collection) + .andExpect(xpath("(//set/setSpec)[2]").string(startsWith("col_"))) + // Second set name should be Collection name + .andExpect(xpath("(//set/setName)[2]").string("Child Collection")) + // No resumption token should be returned + .andExpect(xpath("//resumptionToken").doesNotExist()) + ; + } + + @Test + public void listSetsWithMoreSetsThenMaxSetsPerPage() throws Exception { + //Turn off the authorization system, otherwise we can't make the objects + context.turnOffAuthorisationSystem(); + + // Create 3 Communities (1 as a subcommunity) & 2 Collections + Community firstCommunity = CommunityBuilder.createCommunity(context) + .withName("First Community") + .build(); + Community secondCommunity = CommunityBuilder.createSubCommunity(context, firstCommunity) + .withName("Second Community") + .build(); + CommunityBuilder.createCommunity(context) + .withName("Third Community") + .build(); + CollectionBuilder.createCollection(context, firstCommunity) + .withName("First Collection") + .build(); + CollectionBuilder.createCollection(context, secondCommunity) + .withName("Second Collection") + .build(); + context.restoreAuthSystemState(); + + + // Create a custom XOAI configuration, with maxListSetsSize = 3 for DEFAULT_CONTEXT requests + // (This limits the number of sets returned in a single response) + Configuration xoaiConfig = + new Configuration().withMaxListSetsSize(3) + .withContextConfigurations(new ContextConfiguration(DEFAULT_CONTEXT_PATH)); + // When xoaiManagerResolver.getManager() is called, return a MockXOAIManager based on the above configuration + doReturn(createMockXOAIManager(xoaiConfig)).when(xoaiManagerResolver).getManager(); + + // Call ListSets verb, and verify all 5 Collections/Communities are listed as sets + getClient().perform(get(DEFAULT_CONTEXT).param("verb", "ListSets")) + // Expect 200 response, with valid response date and verb=ListSets + .andExpect(status().isOk()) + .andExpect(xpath("OAI-PMH/responseDate").exists()) + .andExpect(xpath("OAI-PMH/request/@verb").string("ListSets")) + // Expect ONLY 3 (of 5) Sets to be returned + .andExpect(xpath("//set").nodeCount(3)) + // Expect resumption token to exist and be equal to "////3" + .andExpect(xpath("//resumptionToken").string("////3")) + // Expect resumption token to have completeListSize=5 + .andExpect(xpath("//resumptionToken/@completeListSize").number(Double.valueOf(5))) + ; + + // Call ListSets verb, and verify all 5 Collections/Communities are listed as sets + getClient().perform(get(DEFAULT_CONTEXT).param("verb", "ListSets")) + // Expect 200 response, with valid response date and verb=ListSets + .andExpect(status().isOk()) + .andExpect(xpath("OAI-PMH/responseDate").exists()) + .andExpect(xpath("OAI-PMH/request/@verb").string("ListSets")) + // Expect ONLY 3 (of 5) Sets to be returned + .andExpect(xpath("//set").nodeCount(3)) + // Expect resumption token to exist and be equal to "////3" + .andExpect(xpath("//resumptionToken").string("////3")) + // Expect resumption token to have completeListSize=5 + .andExpect(xpath("//resumptionToken/@completeListSize").number(Double.valueOf(5))) + ; + } + + /** + * Create a fake/mock XOAIManager class based on the given xoaiConfig. May be used by above tests + * to provide custom configurations to XOAI (overriding defaults in xoai.xml) + * @param xoaiConfig XOAI Configuration + * @return new XOAIManager initialized with the given Configuration + * @throws ConfigurationException + */ + private XOAIManager createMockXOAIManager(Configuration xoaiConfig) throws ConfigurationException { + return new XOAIManager(filterResolver, resourceResolver, xoaiConfig); + } +} diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rdf/RdfIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rdf/RdfIT.java new file mode 100644 index 0000000000..9cc731ad9c --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rdf/RdfIT.java @@ -0,0 +1,146 @@ +/** + * 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.rdf; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.doReturn; + +import java.net.URI; + +import org.dspace.app.rest.builder.CommunityBuilder; +import org.dspace.app.rest.test.AbstractWebClientIntegrationTest; +import org.dspace.content.Community; +import org.dspace.content.service.SiteService; +import org.dspace.rdf.RDFUtil; +import org.dspace.rdf.conversion.RDFConverter; +import org.dspace.rdf.factory.RDFFactoryImpl; +import org.dspace.rdf.storage.RDFStorage; +import org.dspace.rdf.storage.RDFStorageImpl; +import org.dspace.services.ConfigurationService; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Spy; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.TestPropertySource; + +/** + * Integration test to verify that the /rdf endpoint is responding as a valid RDF endpoint. + * This tests that our dspace-rdf module is running at this endpoint. + *

+ * This is a AbstractWebClientIntegrationTest because testing dspace-rdf requires + * running a web server (as dspace-rdf makes use of Servlets, not Controllers). + *

+ * NOTE: At this time, these ITs do NOT run against a real RDF triplestore. Instead, + * individual tests are expected to mock triplestore responses via a spy-able RDFStorage. + * + * @author Tim Donohue + */ +// Ensure the RDF endpoint IS ENABLED before any tests run. +// This annotation overrides default DSpace config settings loaded into Spring Context +@TestPropertySource(properties = {"rdf.enabled = true"}) +public class RdfIT extends AbstractWebClientIntegrationTest { + + @Autowired + private ConfigurationService configurationService; + + @Autowired + private SiteService siteService; + + @Autowired + private RDFConverter rdfConverter; + + @Autowired + private RDFFactoryImpl rdfFactory; + + // Create a new spy-able instance of RDFStorage. We will use this instance in all below tests (see @Before) + // so that we can fake a triplestore backend. No triplestore is used in these tests. + @Spy + RDFStorage rdfStorage = new RDFStorageImpl(); + + // All RDF paths that we test against + private final String SERIALIZE_PATH = "/rdf/handle"; + private final String REDIRECTION_PATH = "/rdf/resource"; + + @Before + public void onlyRunIfConfigExists() { + // These integration tests REQUIRE that RDFWebConfig is found/available (as this class deploys RDF) + // If this class is not available, the below "Assume" will cause all tests to be SKIPPED + // NOTE: RDFWebConfig is provided by the 'dspace-rdf' module + try { + Class.forName("org.dspace.app.configuration.RDFWebConfig"); + } catch (ClassNotFoundException ce) { + Assume.assumeNoException(ce); + } + + // Change the running RDFFactory to use our spy-able, default instance of RDFStorage + // Again, this lets us fake a triplestore backend in individual tests below. + rdfFactory.setStorage(rdfStorage); + } + + @Test + public void serializationTest() throws Exception { + //Turn off the authorization system, otherwise we can't make the objects + context.turnOffAuthorisationSystem(); + + // Create a Community + Community community = CommunityBuilder.createCommunity(context) + .withName("Test Community") + .build(); + // Ensure Community is written to test DB immediately + context.commit(); + context.restoreAuthSystemState(); + + // Get the RDF identifiers for this new Community & our Site object + String communityIdentifier = RDFUtil.generateIdentifier(context, community); + String siteIdentifier = RDFUtil.generateIdentifier(context, siteService.findSite(context)); + + // Mock an RDF triplestore's response by returning the RDF conversion of our Community + // when rdfStorage.load() is called with the RDF identifier for this Community + doReturn(rdfConverter.convert(context, community)).when(rdfStorage).load(communityIdentifier); + + // Perform a GET request on the RDF /handle path, using our new Community's Handle + ResponseEntity response = getResponseAsString(SERIALIZE_PATH + "/" + community.getHandle()); + // Expect a 200 response code, and text/turtle (RDF Turtle syntax) response + assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); + assertThat(response.getHeaders().getContentType().toString(), equalTo("text/turtle;charset=UTF-8")); + + // Turtle response should include the RDF identifier of Community + assertThat(response.getBody(), containsString(communityIdentifier)); + // Turtle response should also note that this Community is part of our Site object + assertThat(response.getBody(), containsString("dspace:isPartOfRepository <" + siteIdentifier + "> ;")); + } + + @Test + public void redirectionTest() throws Exception { + //Turn off the authorization system, otherwise we can't make the objects + context.turnOffAuthorisationSystem(); + + // Create a Community + Community community = CommunityBuilder.createCommunity(context) + .withName("Test Community") + .build(); + // Ensure Community is written to test DB immediately (so that a lookup via handle will succeed below) + context.commit(); + context.restoreAuthSystemState(); + + String communityHandle = community.getHandle(); + + // Perform a GET request on the RDF /resource path, using this Community's Handle + ResponseEntity response = getResponseAsString(REDIRECTION_PATH + "/" + communityHandle); + // Expect a 303 (See Other) response code, redirecting us to the HTTP URI of the Community + assertThat(response.getStatusCode(), equalTo(HttpStatus.SEE_OTHER)); + // Expect location of redirection to be [dspace.url]/handle/[community_handle] + assertThat(response.getHeaders().getLocation(), equalTo( + URI.create(configurationService.getProperty("dspace.url") + "/handle/" + communityHandle + "/"))); + } +} diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java index 3b3c12c720..bbbfe44a30 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest; +import static com.jayway.jsonpath.JsonPath.read; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata; import static org.hamcrest.Matchers.empty; @@ -21,6 +22,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -37,6 +39,7 @@ import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Collection; import org.dspace.content.Community; +import org.dspace.content.service.CommunityService; import org.dspace.core.Constants; import org.dspace.eperson.EPerson; import org.hamcrest.Matchers; @@ -44,18 +47,22 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; +/** + * Integration Tests against the /api/core/communities endpoint (including any subpaths) + */ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired CommunityConverter communityConverter; + @Autowired + CommunityService communityService; + @Autowired AuthorizeService authorizeService; @Test public void createTest() throws Exception { - context.turnOffAuthorisationSystem(); - ObjectMapper mapper = new ObjectMapper(); CommunityRest comm = new CommunityRest(); // We send a name but the created community should set this to the title @@ -86,41 +93,54 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest comm.setMetadata(metadataRest); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(post("/api/core/communities") + + // Capture the UUID of the created Community (see andDo() below) + AtomicReference idRef = new AtomicReference(); + try { + getClient(authToken).perform(post("/api/core/communities") .content(mapper.writeValueAsBytes(comm)) .contentType(contentType)) - .andExpect(status().isCreated()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.id", not(empty())), - hasJsonPath("$.uuid", not(empty())), - hasJsonPath("$.name", is("Title Text")), - hasJsonPath("$.handle", not(empty())), - hasJsonPath("$.type", is("community")), - hasJsonPath("$._links.collections.href", not(empty())), - hasJsonPath("$._links.logo.href", not(empty())), - hasJsonPath("$._links.subcommunities.href", not(empty())), - hasJsonPath("$._links.self.href", not(empty())), - hasJsonPath("$.metadata", Matchers.allOf( - matchMetadata("dc.description", "

Some cool HTML code here

"), - matchMetadata("dc.description.abstract", + .andExpect(status().isCreated()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.id", not(empty())), + hasJsonPath("$.uuid", not(empty())), + hasJsonPath("$.name", is("Title Text")), + hasJsonPath("$.handle", not(empty())), + hasJsonPath("$.type", is("community")), + hasJsonPath("$._links.collections.href", not(empty())), + hasJsonPath("$._links.logo.href", not(empty())), + hasJsonPath("$._links.subcommunities.href", not(empty())), + hasJsonPath("$._links.self.href", not(empty())), + hasJsonPath("$.metadata", Matchers.allOf( + matchMetadata("dc.description", "

Some cool HTML code here

"), + matchMetadata("dc.description.abstract", "Sample top-level community created via the REST API"), - matchMetadata("dc.description.tableofcontents", "

HTML News

"), - matchMetadata("dc.rights", "Custom Copyright Text"), - matchMetadata("dc.title", "Title Text") - ))))); + matchMetadata("dc.description.tableofcontents", "

HTML News

"), + matchMetadata("dc.rights", "Custom Copyright Text"), + matchMetadata("dc.title", "Title Text") + ) + ) + ))) + // capture "id" returned in JSON response + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id")))); + } finally { + // Delete the created community (cleanup after ourselves!) + CommunityBuilder.deleteCommunity(idRef.get()); + } } + @Test public void createWithParentTest() throws Exception { - context.turnOffAuthorisationSystem(); //We turn off the authorization system in order to create the structure as defined below context.turnOffAuthorisationSystem(); - //** GIVEN ** - //1. A community-collection structure with one parent community with sub-community and one collection. + // Create a parent community to POST a new sub-community to parentCommunity = CommunityBuilder.createCommunity(context) .withName("Parent Community") .build(); + context.restoreAuthSystemState(); ObjectMapper mapper = new ObjectMapper(); CommunityRest comm = new CommunityRest(); @@ -140,23 +160,26 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest new MetadataValueRest("Title Text"))); String authToken = getAuthToken(admin.getEmail(), password); - getClient(authToken).perform(post("/api/core/communities") + // Capture the UUID of the created Community (see andDo() below) + AtomicReference idRef = new AtomicReference(); + try { + getClient(authToken).perform(post("/api/core/communities") .content(mapper.writeValueAsBytes(comm)) .param("parent", parentCommunity.getID().toString()) .contentType(contentType)) - .andExpect(status().isCreated()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.id", not(empty())), - hasJsonPath("$.uuid", not(empty())), - hasJsonPath("$.name", is("Title Text")), - hasJsonPath("$.handle", not(empty())), - hasJsonPath("$.type", is("community")), - hasJsonPath("$._links.collections.href", not(empty())), - hasJsonPath("$._links.logo.href", not(empty())), - hasJsonPath("$._links.subcommunities.href", not(empty())), - hasJsonPath("$._links.self.href", not(empty())), - hasJsonPath("$.metadata", Matchers.allOf( + .andExpect(status().isCreated()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.id", not(empty())), + hasJsonPath("$.uuid", not(empty())), + hasJsonPath("$.name", is("Title Text")), + hasJsonPath("$.handle", not(empty())), + hasJsonPath("$.type", is("community")), + hasJsonPath("$._links.collections.href", not(empty())), + hasJsonPath("$._links.logo.href", not(empty())), + hasJsonPath("$._links.subcommunities.href", not(empty())), + hasJsonPath("$._links.self.href", not(empty())), + hasJsonPath("$.metadata", Matchers.allOf( MetadataMatcher.matchMetadata("dc.description", "

Some cool HTML code here

"), MetadataMatcher.matchMetadata("dc.description.abstract", @@ -167,13 +190,18 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest "Custom Copyright Text"), MetadataMatcher.matchMetadata("dc.title", "Title Text") - ))))); - - - + ) + ) + ))) + // capture "id" returned in JSON response + .andDo(result -> idRef + .set(UUID.fromString(read(result.getResponse().getContentAsString(), "$.id")))); + } finally { + // Delete the created community (cleanup after ourselves!) + CommunityBuilder.deleteCommunity(idRef.get()); + } } - @Test public void createUnauthorizedTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -854,6 +882,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest } + @Test public void patchCommunityMetadataAuthorized() throws Exception { runPatchMetadataTests(admin, 200); } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java index ede63b2814..cdee407282 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/DiscoveryRestControllerIT.java @@ -93,6 +93,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest //We have 4 facets in the default configuration, they need to all be present in the embedded section .andExpect(jsonPath("$._embedded.facets", containsInAnyOrder( FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.dateIssuedFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.hasContentInOriginalBundleFacet(false))) @@ -843,7 +844,13 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest SearchFilterMatcher.dateIssuedFilter(), SearchFilterMatcher.hasContentInOriginalBundleFilter(), SearchFilterMatcher.hasFileNameInOriginalBundleFilter(), - SearchFilterMatcher.hasFileDescriptionInOriginalBundleFilter() + SearchFilterMatcher.hasFileDescriptionInOriginalBundleFilter(), + SearchFilterMatcher.entityTypeFilter(), + SearchFilterMatcher.isAuthorOfPublicationRelation(), + SearchFilterMatcher.isProjectOfPublicationRelation(), + SearchFilterMatcher.isOrgUnitOfPublicationRelation(), + SearchFilterMatcher.isPublicationOfJournalIssueRelation(), + SearchFilterMatcher.isJournalOfPublicationRelation() ))) //These sortOptions need to be present as it's the default in the configuration .andExpect(jsonPath("$.sortOptions", containsInAnyOrder( @@ -925,6 +932,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), FacetEntryMatcher.hasContentInOriginalBundleFacet(false) @@ -1009,6 +1017,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest // facet .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( FacetEntryMatcher.authorFacet(true), + FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), FacetEntryMatcher.hasContentInOriginalBundleFacet(false) @@ -1096,6 +1105,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest // facet .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(true), FacetEntryMatcher.dateIssuedFacet(false), FacetEntryMatcher.hasContentInOriginalBundleFacet(false) @@ -1173,6 +1183,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), FacetEntryMatcher.hasContentInOriginalBundleFacet(false) @@ -1254,6 +1265,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), FacetEntryMatcher.hasContentInOriginalBundleFacet(false) @@ -1330,6 +1342,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), FacetEntryMatcher.hasContentInOriginalBundleFacet(false) @@ -1415,6 +1428,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), FacetEntryMatcher.hasContentInOriginalBundleFacet(false) @@ -1624,7 +1638,8 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest FacetEntryMatcher.authorFacet(true), FacetEntryMatcher.subjectFacet(true), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.entityTypeFacet(false) ))) //There always needs to be a self link available .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -1709,6 +1724,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), FacetEntryMatcher.hasContentInOriginalBundleFacet(false) @@ -1794,6 +1810,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), FacetEntryMatcher.hasContentInOriginalBundleFacet(false) @@ -1954,6 +1971,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), FacetEntryMatcher.hasContentInOriginalBundleFacet(false) @@ -2036,6 +2054,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), FacetEntryMatcher.hasContentInOriginalBundleFacet(false) @@ -2110,6 +2129,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest // property because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), FacetEntryMatcher.hasContentInOriginalBundleFacet(false) @@ -2250,6 +2270,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest // because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), FacetEntryMatcher.hasContentInOriginalBundleFacet(false) @@ -2321,6 +2342,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest // because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), FacetEntryMatcher.hasContentInOriginalBundleFacet(false) @@ -2403,6 +2425,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest ))) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( FacetEntryMatcher.authorFacetWithMinMax(true, "Doe, Jane", "Testing, Works"), + FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(true), FacetEntryMatcher.dateIssuedFacetWithMinMax(false, "1990-02-13", "2010-10-17"), FacetEntryMatcher.hasContentInOriginalBundleFacet(false) @@ -2469,6 +2492,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest .andExpect(jsonPath("$.type", is("discover"))) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( FacetEntryMatcher.authorFacetWithMinMax(true, "Doe, Jane", "Testing, Works"), + FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(true), FacetEntryMatcher.dateIssuedFacetWithMinMax(false, "1990-02-13", "2010-10-17"), FacetEntryMatcher.hasContentInOriginalBundleFacet(false) @@ -2540,6 +2564,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest // because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), FacetEntryMatcher.hasContentInOriginalBundleFacet(false) @@ -2612,6 +2637,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest // because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), FacetEntryMatcher.hasContentInOriginalBundleFacet(false) @@ -2683,6 +2709,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest // because we don't exceed their default limit for a hasMore true (the default is 10) .andExpect(jsonPath("$._embedded.facets", Matchers.containsInAnyOrder( FacetEntryMatcher.authorFacet(false), + FacetEntryMatcher.entityTypeFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), FacetEntryMatcher.hasContentInOriginalBundleFacet(false) @@ -2974,7 +3001,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest /** * This test is intent to verify that inprogress submission (workspaceitem, workflowitem, pool task and claimed * tasks) don't interfers with the standard search - * + * * @throws Exception */ public void discoverSearchObjectsWithInProgressSubmissionTest() throws Exception { @@ -3091,7 +3118,8 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest FacetEntryMatcher.authorFacet(false), FacetEntryMatcher.subjectFacet(false), FacetEntryMatcher.dateIssuedFacet(false), - FacetEntryMatcher.hasContentInOriginalBundleFacet(false) + FacetEntryMatcher.hasContentInOriginalBundleFacet(false), + FacetEntryMatcher.entityTypeFacet(false) ))) //There always needs to be a self link .andExpect(jsonPath("$._links.self.href", containsString("/api/discover/search/objects"))) @@ -3102,7 +3130,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest @Test /** * This test is intent to verify that workspaceitem are only visible to the submitter - * + * * @throws Exception */ public void discoverSearchObjectsWorkspaceConfigurationTest() throws Exception { @@ -3301,7 +3329,7 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest @Test /** * This test is intent to verify that tasks are only visible to the appropriate users (reviewers) - * + * * @throws Exception */ public void discoverSearchObjectsWorkflowConfigurationTest() throws Exception { @@ -3554,4 +3582,4 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest } -} +} \ No newline at end of file diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index 6cbd0168fe..0a7ffe1d35 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -70,9 +70,9 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { getClient(authToken).perform(post("/api/eperson/epersons") .content(mapper.writeValueAsBytes(data)) .contentType(contentType)) - .andExpect(status().isCreated()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$", Matchers.allOf( + .andExpect(status().isCreated()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.allOf( hasJsonPath("$.uuid", not(empty())), // is it what you expect? EPerson.getName() returns the email... //hasJsonPath("$.name", is("Doe John")), diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java new file mode 100644 index 0000000000..5a8706cff8 --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/EntityTypeRestRepositoryIT.java @@ -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; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +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 org.dspace.app.rest.matcher.EntityTypeMatcher; +import org.dspace.app.rest.test.AbstractEntityIntegrationTest; +import org.dspace.content.EntityType; +import org.dspace.content.service.EntityTypeService; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +public class EntityTypeRestRepositoryIT extends AbstractEntityIntegrationTest { + + + @Autowired + private EntityTypeService entityTypeService; + + @Test + public void getAllEntityTypeEndpoint() throws Exception { + //When we call this facets endpoint + getClient().perform(get("/api/core/entitytypes")) + + //We expect a 200 OK status + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.page.totalElements", is(7))) + //There needs to be a self link to this endpoint + .andExpect(jsonPath("$._links.self.href", containsString("api/core/entitytypes"))) + //We have 4 facets in the default configuration, they need to all be present in the embedded section + .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( + EntityTypeMatcher + .matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Publication")), + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Person")), + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Project")), + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "OrgUnit")), + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Journal")), + EntityTypeMatcher + .matchEntityTypeEntry(entityTypeService.findByEntityType(context, "JournalVolume")), + EntityTypeMatcher + .matchEntityTypeEntry(entityTypeService.findByEntityType(context, "JournalIssue")) + ))); + } + + @Test + public void getAllEntityTypeEndpointWithPaging() throws Exception { + getClient().perform(get("/api/core/entitytypes").param("size", "5")) + + //We expect a 200 OK status + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.page.size", is(5))) + .andExpect(jsonPath("$.page.totalElements", is(7))) + .andExpect(jsonPath("$.page.totalPages", is(2))) + //There needs to be a self link to this endpoint + .andExpect(jsonPath("$._links.self.href", containsString("api/core/entitytypes"))) + //We have 4 facets in the default configuration, they need to all be present in the embedded section + .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( + EntityTypeMatcher + .matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Publication")), + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Person")), + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Project")), + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "OrgUnit")), + EntityTypeMatcher.matchEntityTypeEntry(entityTypeService.findByEntityType(context, "Journal")) + ))); + + getClient().perform(get("/api/core/entitytypes").param("size", "5").param("page", "1")) + + //We expect a 200 OK status + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.page.size", is(5))) + .andExpect(jsonPath("$.page.totalElements", is(7))) + .andExpect(jsonPath("$.page.totalPages", is(2))) + .andExpect(jsonPath("$.page.number", is(1))) + //There needs to be a self link to this endpoint + .andExpect(jsonPath("$._links.self.href", containsString("api/core/entitytypes"))) + //We have 4 facets in the default configuration, they need to all be present in the embedded section + .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( + EntityTypeMatcher + .matchEntityTypeEntry(entityTypeService.findByEntityType(context, "JournalVolume")), + EntityTypeMatcher + .matchEntityTypeEntry(entityTypeService.findByEntityType(context, "JournalIssue")) + ))); + } + + @Test + public void retrieveOneEntityType() throws Exception { + EntityType entityType = entityTypeService.findByEntityType(context, "Publication"); + getClient().perform(get("/api/core/entitytypes/" + entityType.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", EntityTypeMatcher.matchEntityTypeEntry(entityType))); + } + + @Test + public void retrieveOneEntityTypeThatDoesNotExist() throws Exception { + getClient().perform(get("/api/core/entitytypes/" + 5555)) + .andExpect(status().isNotFound()); + } +} \ No newline at end of file diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/InitializeEntitiesIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/InitializeEntitiesIT.java new file mode 100644 index 0000000000..c3d264f67d --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/InitializeEntitiesIT.java @@ -0,0 +1,186 @@ +/** + * 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.CoreMatchers.containsString; +import static org.hamcrest.Matchers.containsInAnyOrder; +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.io.File; +import java.util.Iterator; +import java.util.List; + +import org.dspace.app.rest.matcher.RelationshipTypeMatcher; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.content.EntityType; +import org.dspace.content.Relationship; +import org.dspace.content.RelationshipType; +import org.dspace.content.service.EntityTypeService; +import org.dspace.content.service.RelationshipService; +import org.dspace.content.service.RelationshipTypeService; +import org.dspace.services.ConfigurationService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * This class is used to verify the behavior of the {@link org.dspace.app.util.InitializeEntities} script + * It will take the relationship-types.xml as initial input and check if all the objects that created properly. + * It will then also update and verify these objects by using a second XML to verify the update behaviour + * of this script. + * This will ensure that all the EntityTypes and RelationshipTypes are instantiated correctly. + */ +public class InitializeEntitiesIT extends AbstractControllerIntegrationTest { + + @Autowired + private RelationshipTypeService relationshipTypeService; + + @Autowired + private EntityTypeService entityTypeService; + + @Autowired + private RelationshipService relationshipService; + + @Autowired + private ConfigurationService configurationService; + + /** + * Build the relationships using the standard test XML with the initialize-entities script + */ + @Before + public void setup() throws Exception { + + //Set up the database for the next test + String pathToFile = configurationService.getProperty("dspace.dir") + + File.separator + "config" + File.separator + "entities" + File.separator + "relationship-types.xml"; + runDSpaceScript("initialize-entities", "-f", pathToFile); + + } + + @After + public void destroy() throws Exception { + //Clean up the database for the next test + context.turnOffAuthorisationSystem(); + List relationshipTypeList = relationshipTypeService.findAll(context); + List entityTypeList = entityTypeService.findAll(context); + List relationships = relationshipService.findAll(context); + + Iterator relationshipIterator = relationships.iterator(); + while (relationshipIterator.hasNext()) { + Relationship relationship = relationshipIterator.next(); + relationshipIterator.remove(); + relationshipService.delete(context, relationship); + } + + Iterator relationshipTypeIterator = relationshipTypeList.iterator(); + while (relationshipTypeIterator.hasNext()) { + RelationshipType relationshipType = relationshipTypeIterator.next(); + relationshipTypeIterator.remove(); + relationshipTypeService.delete(context, relationshipType); + } + + Iterator entityTypeIterator = entityTypeList.iterator(); + while (entityTypeIterator.hasNext()) { + EntityType entityType = entityTypeIterator.next(); + entityTypeIterator.remove(); + entityTypeService.delete(context, entityType); + } + + super.destroy(); + } + + /** + * Verifies that the initialize-entities script ran properly and that the objects are created properly + * @throws Exception + */ + @Test + public void getAllRelationshipTypesTest() throws Exception { + List relationshipTypes = relationshipTypeService.findAll(context); + + getClient().perform(get("/api/core/relationshiptypes")) + + //We expect a 200 OK status + .andExpect(status().isOk()) + //10 relationship types should be created + .andExpect(jsonPath("$.page.totalElements", is(10))) + //There needs to be a self link to this endpoint + .andExpect(jsonPath("$._links.self.href", containsString("api/core/relationshiptypes"))) + //We have 10 relationship types, they need to all be present in the embedded section + .andExpect(jsonPath("$._embedded.relationshiptypes", containsInAnyOrder( + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(0)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(1)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(2)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(3)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(4)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(5)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(6)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(7)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(8)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(9))) + )); + + //Verify the left min cardinality of the first relationship type (isAuthorOfPublication) is 0 + getClient().perform(get("/api/core/relationshiptypes/" + relationshipTypes.get(0).getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.leftMinCardinality", is(0))); + } + + /** + * Verify whether the initialize-entities script can update the relationship types correctly + */ + @Test + public void updateRelationshipTypesTest() throws Exception { + List relationshipTypes = relationshipTypeService.findAll(context); + + //Update the relationships using a different test XML with the initialize-entities script + String pathToFile = configurationService.getProperty("dspace.dir") + + File.separator + "config" + File.separator + "entities" + File.separator + "relationship-types-update.xml"; + runDSpaceScript("initialize-entities", "-f", pathToFile); + + // This is a helper object to compare whether the update was successful. We're simply taking the first + // RelationshipType object in the list and altering this by setting the LeftMinCardinality on 10. We've + // Made sure that this RelationshipType object is altered in the same way in the relationship-types-update.xml + // File and thus by running the script, the RelationshipType object in the Database should be the same as our + // alteredRelationshipType object that we just created. + // It's important to note that this object will not alter anything in the Database, this object is merely + // made for the comparison between the REST response and we expect to have happened through the script + RelationshipType alteredRelationshipType = relationshipTypes.get(0); + alteredRelationshipType.setLeftMinCardinality(10); + getClient().perform(get("/api/core/relationshiptypes")) + + //We expect a 200 OK status + .andExpect(status().isOk()) + //10 relationship types should remain present (no duplicates created) + .andExpect(jsonPath("$.page.totalElements", is(10))) + //There needs to be a self link to this endpoint + .andExpect(jsonPath("$._links.self.href", containsString("api/core/relationshiptypes"))) + //We have 10 relationship types, they need to all be present in the embedded section + //Verify the left min cardinality of the isAuthorOfPublication has been updated to 10 + .andExpect(jsonPath("$._embedded.relationshiptypes", containsInAnyOrder( + RelationshipTypeMatcher.matchRelationshipTypeEntry(alteredRelationshipType), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(1)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(2)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(3)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(4)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(5)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(6)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(7)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(8)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(9))) + )); + + //Verify the left min cardinality of the first relationship type (isAuthorOfPublication) has been updated to 10 + getClient().perform(get("/api/core/relationshiptypes/" + relationshipTypes.get(0).getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.leftMinCardinality", is(10))); + } +} diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java new file mode 100644 index 0000000000..aaa9dfa1d0 --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java @@ -0,0 +1,2164 @@ +/** + * 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.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.builder.CollectionBuilder; +import org.dspace.app.rest.builder.CommunityBuilder; +import org.dspace.app.rest.builder.EPersonBuilder; +import org.dspace.app.rest.builder.ItemBuilder; +import org.dspace.app.rest.builder.RelationshipBuilder; +import org.dspace.app.rest.matcher.PageMatcher; +import org.dspace.app.rest.matcher.RelationshipMatcher; +import org.dspace.app.rest.test.AbstractEntityIntegrationTest; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.Relationship; +import org.dspace.content.RelationshipType; +import org.dspace.content.service.EntityTypeService; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.RelationshipTypeService; +import org.dspace.core.Constants; +import org.dspace.core.I18nUtil; +import org.dspace.eperson.EPerson; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MvcResult; + +public class RelationshipRestRepositoryIT extends AbstractEntityIntegrationTest { + + @Autowired + private RelationshipTypeService relationshipTypeService; + + @Autowired + private EntityTypeService entityTypeService; + + @Autowired + private AuthorizeService authorizeService; + + @Autowired + private ItemService itemService; + + + @Test + public void findAllRelationshipTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + Collection col3 = CollectionBuilder.createCollection(context, child1).withName("OrgUnits").build(); + + Item auhor1 = ItemBuilder.createItem(context, col1) + .withTitle("Author1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .withRelationshipType("Person") + .build(); + + Item author2 = ItemBuilder.createItem(context, col2) + .withTitle("Author2") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria") + .withRelationshipType("Person") + .build(); + + Item author3 = ItemBuilder.createItem(context, col2) + .withTitle("Author3") + .withIssueDate("2016-02-13") + .withAuthor("Maybe, Maybe") + .withRelationshipType("Person") + .build(); + + Item orgUnit1 = ItemBuilder.createItem(context, col3) + .withTitle("OrgUnit1") + .withAuthor("Testy, TEst") + .withIssueDate("2015-01-01") + .withRelationshipType("OrgUnit") + .build(); + + Item project1 = ItemBuilder.createItem(context, col3) + .withTitle("Project1") + .withAuthor("Testy, TEst") + .withIssueDate("2015-01-01") + .withRelationshipType("Project") + .build(); + + Item publication = ItemBuilder.createItem(context, col3) + .withTitle("Publication1") + .withAuthor("Testy, TEst") + .withIssueDate("2015-01-01") + .withRelationshipType("Publication") + .build(); + + + RelationshipType isOrgUnitOfPersonRelationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Person"), + entityTypeService.findByEntityType(context, "OrgUnit"), + "isOrgUnitOfPerson", "isPersonOfOrgUnit"); + RelationshipType isOrgUnitOfProjectRelationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Project"), + entityTypeService.findByEntityType(context, "OrgUnit"), + "isOrgUnitOfProject", "isProjectOfOrgUnit"); + RelationshipType isAuthorOfPublicationRelationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), + "isAuthorOfPublication", "isPublicationOfAuthor"); + + Relationship relationship1 = RelationshipBuilder + .createRelationshipBuilder(context, auhor1, orgUnit1, isOrgUnitOfPersonRelationshipType).build(); + + Relationship relationship2 = RelationshipBuilder + .createRelationshipBuilder(context, project1, orgUnit1, isOrgUnitOfProjectRelationshipType).build(); + + Relationship relationship3 = RelationshipBuilder + .createRelationshipBuilder(context, publication, auhor1, isAuthorOfPublicationRelationshipType).build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/relationships")) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page", + is(PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 3)))) + .andExpect(jsonPath("$._embedded.relationships", containsInAnyOrder( + RelationshipMatcher.matchRelationship(relationship1), + RelationshipMatcher.matchRelationship(relationship2), + RelationshipMatcher.matchRelationship(relationship3) + ))) + ; + + getClient().perform(get("/api/core/relationships").param("size", "2")) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page", + is(PageMatcher.pageEntryWithTotalPagesAndElements(0, 2, 2, 3)))) + .andExpect(jsonPath("$._embedded.relationships", containsInAnyOrder( + RelationshipMatcher.matchRelationship(relationship1), + RelationshipMatcher.matchRelationship(relationship2) + ))) + ; + + getClient().perform(get("/api/core/relationships").param("size", "2").param("page", "1")) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page", + is(PageMatcher.pageEntryWithTotalPagesAndElements(1, 2, 2, 3)))) + .andExpect(jsonPath("$._embedded.relationships", contains( + RelationshipMatcher.matchRelationship(relationship3) + ))) + ; + + } + + @Test + public void createRelationshipWriteAccessLeftItem() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + Collection col3 = CollectionBuilder.createCollection(context, child1).withName("OrgUnits").build(); + + Item author1 = ItemBuilder.createItem(context, col1) + .withTitle("Author1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .withRelationshipType("Person") + .build(); + + Item publication = ItemBuilder.createItem(context, col3) + .withTitle("Publication1") + .withAuthor("Testy, TEst") + .withIssueDate("2015-01-01") + .withRelationshipType("Publication") + .build(); + + RelationshipType isAuthorOfPublicationRelationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), + "isAuthorOfPublication", "isPublicationOfAuthor"); + + EPerson user = EPersonBuilder.createEPerson(context) + .withNameInMetadata("first", "last") + .withEmail("testaze@gmail.com") + .withPassword(password) + .withLanguage(I18nUtil.getDefaultLocale().getLanguage()) + .build(); + context.setCurrentUser(user); + + authorizeService.addPolicy(context, publication, Constants.WRITE, user); + + context.restoreAuthSystemState(); + + String token = getAuthToken(user.getEmail(), password); + + MvcResult mvcResult = getClient(token).perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author1.getID())) + .andExpect(status().isCreated()) + .andReturn(); + + ObjectMapper mapper = new ObjectMapper(); + + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String firstRelationshipIdString = String.valueOf(map.get("id")); + + getClient().perform(get("/api/core/relationships/" + firstRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.leftId", is(publication.getID().toString()))) + .andExpect(jsonPath("$.rightId", is(author1.getID().toString()))); + + } + + @Test + public void createRelationshipWriteAccessRightItem() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + Collection col3 = CollectionBuilder.createCollection(context, child1).withName("OrgUnits").build(); + + Item author1 = ItemBuilder.createItem(context, col1) + .withTitle("Author1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .withRelationshipType("Person") + .build(); + + Item publication = ItemBuilder.createItem(context, col3) + .withTitle("Publication1") + .withAuthor("Testy, TEst") + .withIssueDate("2015-01-01") + .withRelationshipType("Publication") + .build(); + + RelationshipType isAuthorOfPublicationRelationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), + "isAuthorOfPublication", "isPublicationOfAuthor"); + + EPerson user = EPersonBuilder.createEPerson(context) + .withNameInMetadata("first", "last") + .withEmail("testaze@gmail.com") + .withPassword(password) + .withLanguage(I18nUtil.getDefaultLocale().getLanguage()) + .build(); + context.setCurrentUser(user); + + authorizeService.addPolicy(context, author1, Constants.WRITE, user); + context.restoreAuthSystemState(); + + String token = getAuthToken(user.getEmail(), password); + + MvcResult mvcResult = getClient(token).perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author1.getID())) + .andExpect(status().isCreated()) + .andReturn(); + + ObjectMapper mapper = new ObjectMapper(); + + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String firstRelationshipIdString = String.valueOf(map.get("id")); + + getClient().perform(get("/api/core/relationships/" + firstRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.leftId", is(publication.getID().toString()))) + .andExpect(jsonPath("$.rightId", is(author1.getID().toString()))); + } + + + @Test + public void createRelationshipNoWriteAccess() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + Collection col3 = CollectionBuilder.createCollection(context, child1).withName("OrgUnits").build(); + + Item author1 = ItemBuilder.createItem(context, col1) + .withTitle("Author1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .withRelationshipType("Person") + .build(); + + Item publication = ItemBuilder.createItem(context, col3) + .withTitle("Publication1") + .withAuthor("Testy, TEst") + .withIssueDate("2015-01-01") + .withRelationshipType("Publication") + .build(); + + RelationshipType isAuthorOfPublicationRelationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), + "isAuthorOfPublication", "isPublicationOfAuthor"); + + EPerson user = EPersonBuilder.createEPerson(context) + .withNameInMetadata("first", "last") + .withEmail("testaze@gmail.com") + .withPassword(password) + .withLanguage(I18nUtil.getDefaultLocale().getLanguage()) + .build(); + context.setCurrentUser(user); + context.restoreAuthSystemState(); + + String token = getAuthToken(user.getEmail(), password); + + MvcResult mvcResult = getClient(token).perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author1.getID())) + .andExpect(status().isForbidden()) + .andReturn(); + + } + + /** + * This method will test the addition of a mixture of plain-text metadatavalues and relationships to then + * verify that the place property is still being handled correctly. + * @throws Exception + */ + @Test + public void addRelationshipsAndMetadataToValidatePlaceTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + Collection col3 = CollectionBuilder.createCollection(context, child1).withName("OrgUnits").build(); + + Item author1 = ItemBuilder.createItem(context, col1) + .withTitle("Author1") + .withIssueDate("2017-10-17") + .withPersonIdentifierFirstName("Donald") + .withPersonIdentifierLastName("Smith") + .withRelationshipType("Person") + .build(); + + Item author2 = ItemBuilder.createItem(context, col2) + .withTitle("Author2") + .withIssueDate("2016-02-13") + .withPersonIdentifierFirstName("Maria") + .withPersonIdentifierLastName("Smith") + .withRelationshipType("Person") + .build(); + + Item author3 = ItemBuilder.createItem(context, col2) + .withTitle("Author3") + .withIssueDate("2016-02-13") + .withPersonIdentifierFirstName("Maybe") + .withPersonIdentifierLastName("Maybe") + .withRelationshipType("Person") + .build(); + + Item publication = ItemBuilder.createItem(context, col3) + .withTitle("Publication1") + .withIssueDate("2015-01-01") + .withRelationshipType("Publication") + .build(); + RelationshipType isAuthorOfPublicationRelationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), + "isAuthorOfPublication", "isPublicationOfAuthor"); + + String adminToken = getAuthToken(admin.getEmail(), password); + + context.restoreAuthSystemState(); + // Here we create our first Relationship to the Publication to give it a dc.contributor.author virtual + // metadata field. + MvcResult mvcResult = getClient(adminToken).perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author1.getID())) + .andExpect(status().isCreated()) + .andReturn(); + ObjectMapper mapper = new ObjectMapper(); + + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String firstRelationshipIdString = String.valueOf(map.get("id")); + + // Here we call the relationship and verify that the relationship's leftplace is 0 + getClient(adminToken).perform(get("/api/core/relationships/" + firstRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("leftPlace", is(0))); + + context.turnOffAuthorisationSystem(); + + // Make sure we grab the latest instance of the Item from the database + publication = itemService.find(context, publication.getID()); + // Add a plain text dc.contributor.author value + itemService.addMetadata(context, publication, "dc", "contributor", "author", Item.ANY, "plain text"); + itemService.update(context, publication); + + List list = itemService.getMetadata(publication, "dc", "contributor", "author", Item.ANY); + + // Ensure that the list of dc.contributor.author values now holds two values ("Smith, Donald" and "plain text") + assertEquals(2, list.size()); + for (MetadataValue mdv : list) { + // Here we want to ensure that the "plain text" metadatavalue has place 1 because it was added later than + // the Relationship, so the "Smith, Donald" should have place 0 and "plain text" should have place 1 + if (StringUtils.equals(mdv.getValue(), "plain text")) { + assertEquals(1, mdv.getPlace()); + } + } + // Testing what was describe above + MetadataValue author0MD = list.get(0); + assertEquals("Smith, Donald", author0MD.getValue()); + MetadataValue author1MD = list.get(1); + assertEquals("plain text", author1MD.getValue()); + + + context.restoreAuthSystemState(); + // Verfiy the leftPlace of our relationship is still 0. + getClient(adminToken).perform(get("/api/core/relationships/" + firstRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("leftPlace", is(0))); + + // Create another Relationship for the Publication, thus creating a third dc.contributor.author mdv + mvcResult = getClient(adminToken).perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author2.getID())) + .andExpect(status().isCreated()) + .andReturn(); + + content = mvcResult.getResponse().getContentAsString(); + map = mapper.readValue(content, Map.class); + // Grab the ID for the second relationship for future calling in the rest + String secondRelationshipIdString = String.valueOf(map.get("id")); + + list = itemService.getMetadata(publication, "dc", "contributor", "author", Item.ANY); + // Ensure that we now have three dc.contributor.author mdv ("Smith, Donald", "plain text", "Smith, Maria" + // In that order which will be checked below the rest call + assertEquals(3, list.size()); + // Perform the REST call to the relationship to ensure its leftPlace is 2 even though it's only the second + // Relationship. Note that leftPlace 1 was skipped due to the dc.contributor.author plain text value and + // This is expected behaviour and should be tested + getClient(adminToken).perform(get("/api/core/relationships/" + secondRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("leftPlace", is(2))); + + author0MD = list.get(0); + assertEquals("Smith, Donald", author0MD.getValue()); + author1MD = list.get(1); + assertEquals("plain text", author1MD.getValue()); + MetadataValue author2MD = list.get(2); + assertEquals("Smith, Maria", author2MD.getValue()); + + context.turnOffAuthorisationSystem(); + // Ensure we have the latest instance of the Item from the database + publication = itemService.find(context, publication.getID()); + // Add a fourth dc.contributor.author mdv + itemService.addMetadata(context, publication, "dc", "contributor", "author", Item.ANY, "plain text two"); + itemService.update(context, publication); + + context.restoreAuthSystemState(); + list = itemService.getMetadata(publication, "dc", "contributor", "author", Item.ANY); + + // Assert that the list of dc.contributor.author mdv is now of size 4 in the following order: + // "Smith, Donald", "plain text", "Smith, Maria", "plain text two" + assertEquals(4, list.size()); + for (MetadataValue mdv : list) { + if (StringUtils.equals(mdv.getValue(), "plain text two")) { + assertEquals(3, mdv.getPlace()); + } + } + + author0MD = list.get(0); + assertEquals("Smith, Donald", author0MD.getValue()); + author1MD = list.get(1); + assertEquals("plain text", author1MD.getValue()); + author2MD = list.get(2); + assertEquals("Smith, Maria", author2MD.getValue()); + MetadataValue author3MD = list.get(3); + assertEquals("plain text two", author3MD.getValue()); + + + // Create the third Relationship thus adding a fifth dc.contributor.author mdv + mvcResult = getClient(adminToken).perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author3.getID())) + .andExpect(status().isCreated()) + .andReturn(); + + content = mvcResult.getResponse().getContentAsString(); + map = mapper.readValue(content, Map.class); + // Save the third Relationship's ID for future calling + String thirdRelationshipIdString = String.valueOf(map.get("id")); + + list = itemService.getMetadata(publication, "dc", "contributor", "author", Item.ANY); + // Assert that our dc.contributor.author mdv list is now of size 5 + assertEquals(5, list.size()); + // Assert that the third Relationship has leftPlace 4, even though 3 relationships were created. + // This is because the plain text values 'occupy' place 1 and 3. + getClient(adminToken).perform(get("/api/core/relationships/" + thirdRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("leftPlace", is(4))); + + // Assert that the list is of size 5 and in the following order: + // "Smith, Donald", "plain text", "Smith, Maria", "plain text two", "Maybe, Maybe" + // Thus the order they were added in + author0MD = list.get(0); + assertEquals("Smith, Donald", author0MD.getValue()); + author1MD = list.get(1); + assertEquals("plain text", author1MD.getValue()); + author2MD = list.get(2); + assertEquals("Smith, Maria", author2MD.getValue()); + author3MD = list.get(3); + assertEquals("plain text two", author3MD.getValue()); + MetadataValue author4MD = list.get(4); + assertEquals("Maybe, Maybe", author4MD.getValue()); + + context.turnOffAuthorisationSystem(); + // The following additions of Metadata will perform the same sequence of logic and tests as described above + publication = itemService.find(context, publication.getID()); + itemService.addMetadata(context, publication, "dc", "contributor", "author", Item.ANY, "plain text three"); + itemService.update(context, publication); + + context.restoreAuthSystemState(); + + list = itemService.getMetadata(publication, "dc", "contributor", "author", Item.ANY); + + assertEquals(6, list.size()); + for (MetadataValue mdv : list) { + if (StringUtils.equals(mdv.getValue(), "plain text three")) { + assertEquals(5, mdv.getPlace()); + } + } + + author0MD = list.get(0); + assertEquals("Smith, Donald", author0MD.getValue()); + author1MD = list.get(1); + assertEquals("plain text", author1MD.getValue()); + author2MD = list.get(2); + assertEquals("Smith, Maria", author2MD.getValue()); + author3MD = list.get(3); + assertEquals("plain text two", author3MD.getValue()); + author4MD = list.get(4); + assertEquals("Maybe, Maybe", author4MD.getValue()); + MetadataValue author5MD = list.get(5); + assertEquals("plain text three", author5MD.getValue()); + + context.turnOffAuthorisationSystem(); + + publication = itemService.find(context, publication.getID()); + itemService.addMetadata(context, publication, "dc", "contributor", "author", Item.ANY, "plain text four"); + itemService.addMetadata(context, publication, "dc", "contributor", "author", Item.ANY, "plain text five"); + itemService.addMetadata(context, publication, "dc", "contributor", "author", Item.ANY, "plain text six"); + itemService.addMetadata(context, publication, "dc", "contributor", "author", Item.ANY, "plain text seven"); + itemService.update(context, publication); + + context.restoreAuthSystemState(); + list = itemService.getMetadata(publication, "dc", "contributor", "author", Item.ANY); + + assertEquals(10, list.size()); + for (MetadataValue mdv : list) { + if (StringUtils.equals(mdv.getValue(), "plain text four")) { + assertEquals(6, mdv.getPlace()); + } + if (StringUtils.equals(mdv.getValue(), "plain text five")) { + assertEquals(7, mdv.getPlace()); + } + if (StringUtils.equals(mdv.getValue(), "plain text six")) { + assertEquals(8, mdv.getPlace()); + } + if (StringUtils.equals(mdv.getValue(), "plain text seven")) { + assertEquals(9, mdv.getPlace()); + } + } + + + author0MD = list.get(0); + assertEquals("Smith, Donald", author0MD.getValue()); + author1MD = list.get(1); + assertEquals("plain text", author1MD.getValue()); + author2MD = list.get(2); + assertEquals("Smith, Maria", author2MD.getValue()); + author3MD = list.get(3); + assertEquals("plain text two", author3MD.getValue()); + author4MD = list.get(4); + assertEquals("Maybe, Maybe", author4MD.getValue()); + author5MD = list.get(5); + assertEquals("plain text three", author5MD.getValue()); + MetadataValue author6MD = list.get(6); + assertEquals("plain text four", author6MD.getValue()); + MetadataValue author7MD = list.get(7); + assertEquals("plain text five", author7MD.getValue()); + MetadataValue author8MD = list.get(8); + assertEquals("plain text six", author8MD.getValue()); + MetadataValue author9MD = list.get(9); + assertEquals("plain text seven", author9MD.getValue()); + + list = itemService.getMetadata(publication, "dc", "contributor", Item.ANY, Item.ANY); + assertEquals(10, list.size()); //same size as authors + list = itemService.getMetadata(publication, "dc", Item.ANY, Item.ANY, Item.ANY); + assertEquals(16, list.size()); //also includes title, 4 date fields, uri + list = itemService.getMetadata(publication, Item.ANY, Item.ANY, Item.ANY, Item.ANY); + assertEquals(20, list.size()); //also includes type and 3 relation.isAuthorOfPublication values + } + + /** + * This method will test the deletion of a plain-text metadatavalue to then + * verify that the place property is still being handled correctly. + * @throws Exception + */ + @Test + public void deleteMetadataValueAndValidatePlace() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + Collection col3 = CollectionBuilder.createCollection(context, child1).withName("OrgUnits").build(); + + Item author1 = ItemBuilder.createItem(context, col1) + .withTitle("Author1") + .withIssueDate("2017-10-17") + .withPersonIdentifierFirstName("Donald") + .withPersonIdentifierLastName("Smith") + .withRelationshipType("Person") + .build(); + + Item author2 = ItemBuilder.createItem(context, col2) + .withTitle("Author2") + .withIssueDate("2016-02-13") + .withPersonIdentifierFirstName("Maria") + .withPersonIdentifierLastName("Smith") + .withRelationshipType("Person") + .build(); + + Item author3 = ItemBuilder.createItem(context, col2) + .withTitle("Author3") + .withIssueDate("2016-02-13") + .withPersonIdentifierFirstName("Maybe") + .withPersonIdentifierLastName("Maybe") + .withRelationshipType("Person") + .build(); + + Item publication = ItemBuilder.createItem(context, col3) + .withTitle("Publication1") + .withIssueDate("2015-01-01") + .withRelationshipType("Publication") + .build(); + RelationshipType isAuthorOfPublicationRelationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), + "isAuthorOfPublication", "isPublicationOfAuthor"); + + String adminToken = getAuthToken(admin.getEmail(), password); + + // First create the structure of 5 metadatavalues just like the additions test. + context.restoreAuthSystemState(); + // This post request will add a first relationship to the publiction and thus create a first set of metadata + // For the author values, namely "Donald Smith" + MvcResult mvcResult = getClient(adminToken).perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author1.getID())) + .andExpect(status().isCreated()) + .andReturn(); + ObjectMapper mapper = new ObjectMapper(); + + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String firstRelationshipIdString = String.valueOf(map.get("id")); + + // This test will double check that the leftPlace for this relationship is indeed 0 + getClient(adminToken).perform(get("/api/core/relationships/" + firstRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("leftPlace", is(0))); + + + context.turnOffAuthorisationSystem(); + // We retrieve the publication again to ensure that we have the latest DB object of it + publication = itemService.find(context, publication.getID()); + // Add a plain text metadatavalue to the publication + // After this addition, the list of authors should like like "Donald Smith", "plain text" + itemService.addMetadata(context, publication, "dc", "contributor", "author", Item.ANY, "plain text"); + itemService.update(context, publication); + context.restoreAuthSystemState(); + List list = itemService.getMetadata(publication, "dc", "contributor", "author", Item.ANY); + + for (MetadataValue mdv : list) { + if (StringUtils.equals(mdv.getValue(), "plain text")) { + // Ensure that this is indeed the second metadatavalue in the list of authors for the publication + assertEquals(1, mdv.getPlace()); + } + } + + // This test checks again that the first relationship is still on leftplace 0 and not altered + // Because of the MetadataValue addition + getClient(adminToken).perform(get("/api/core/relationships/" + firstRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("leftPlace", is(0))); + + // Creates another Relationship for the Publication and thus adding a third metadata value for the author + mvcResult = getClient(adminToken).perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author2.getID())) + .andExpect(status().isCreated()) + .andReturn(); + + content = mvcResult.getResponse().getContentAsString(); + map = mapper.readValue(content, Map.class); + String secondRelationshipIdString = String.valueOf(map.get("id")); + + // This test verifies that the newly created Relationship is on leftPlace 2, because the first relationship + // is on leftPlace 0 and the plain text metadataValue occupies the place 1 + getClient(adminToken).perform(get("/api/core/relationships/" + secondRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("leftPlace", is(2))); + context.turnOffAuthorisationSystem(); + // Get the publication from the DB again to ensure that we have the latest object + publication = itemService.find(context, publication.getID()); + // Add a fourth metadata value to the publication + itemService.addMetadata(context, publication, "dc", "contributor", "author", Item.ANY, "plain text two"); + itemService.update(context, publication); + context.restoreAuthSystemState(); + list = itemService.getMetadata(publication, "dc", "contributor", "author", Item.ANY); + + for (MetadataValue mdv : list) { + if (StringUtils.equals(mdv.getValue(), "plain text two")) { + // Ensure that this plain text metadata value is on the fourth place (place 3) for the publication + assertEquals(3, mdv.getPlace()); + } + } + + // The list should currently look like this: "Donald Smith", "plain text", "Maria Smith", "plain text two" + + // This creates a third relationship for the publication and thus adding a fifth value for author metadatavalues + mvcResult = getClient(adminToken).perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author3.getID())) + .andExpect(status().isCreated()) + .andReturn(); + + content = mvcResult.getResponse().getContentAsString(); + map = mapper.readValue(content, Map.class); + String thirdRelationshipIdString = String.valueOf(map.get("id")); + + // This verifies that the newly created third relationship is on leftPlace 4. + getClient(adminToken).perform(get("/api/core/relationships/" + thirdRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("leftPlace", is(4))); + + context.turnOffAuthorisationSystem(); + publication = itemService.find(context, publication.getID()); + // Create another plain text metadata value on the publication + itemService.addMetadata(context, publication, "dc", "contributor", "author", Item.ANY, "plain text three"); + itemService.update(context, publication); + context.restoreAuthSystemState(); + list = itemService.getMetadata(publication, "dc", "contributor", "author", Item.ANY); + + for (MetadataValue mdv : list) { + if (StringUtils.equals(mdv.getValue(), "plain text three")) { + // Verify that this plain text value is indeed the 6th author in the list (place 5) + assertEquals(5, mdv.getPlace()); + } + } + + // Now we will have a dc.contributor.author metadatavalue list of size 6 in the following order: + // "Smith, Donald", "plain text", "Smith, Maria", "plain text two", "Maybe, Maybe", "plain text three" + List authors = itemService.getMetadata(publication, "dc", "contributor", "author", Item.ANY); + List listToRemove = new LinkedList<>(); + for (MetadataValue metadataValue : authors) { + if (StringUtils.equals(metadataValue.getValue(), "plain text two")) { + listToRemove.add(metadataValue); + } + } + + context.turnOffAuthorisationSystem(); + // Remove the "plain text two" metadatavalue. Ensure that all mdvs prior to that in the list are unchanged + // And ensure that the ones coming after this mdv have its place lowered by one. + itemService.removeMetadataValues(context, publication, listToRemove); + + itemService.update(context, publication); + context.restoreAuthSystemState(); + list = itemService.getMetadata(publication, "dc", "contributor", "author", Item.ANY); + + for (MetadataValue mdv : list) { + if (StringUtils.equals(mdv.getValue(), "plain text")) { + assertEquals(1, mdv.getPlace()); + } + } + list = itemService.getMetadata(publication, "dc", "contributor", "author", Item.ANY); + + for (MetadataValue mdv : list) { + if (StringUtils.equals(mdv.getValue(), "plain text three")) { + assertEquals(4, mdv.getPlace()); + } + } + + getClient(adminToken).perform(get("/api/core/relationships/" + firstRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("leftPlace", is(0))); + getClient(adminToken).perform(get("/api/core/relationships/" + secondRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("leftPlace", is(2))); + getClient(adminToken).perform(get("/api/core/relationships/" + thirdRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("leftPlace", is(3))); + + } + + /** + * This method will test the deletion of a Relationship to then + * verify that the place property is still being handled correctly. + * @throws Exception + */ + @Test + public void deleteRelationshipsAndValidatePlace() throws Exception { + + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + Collection col3 = CollectionBuilder.createCollection(context, child1).withName("OrgUnits").build(); + + Item author1 = ItemBuilder.createItem(context, col1) + .withTitle("Author1") + .withIssueDate("2017-10-17") + .withPersonIdentifierFirstName("Donald") + .withPersonIdentifierLastName("Smith") + .withRelationshipType("Person") + .build(); + + Item author2 = ItemBuilder.createItem(context, col2) + .withTitle("Author2") + .withIssueDate("2016-02-13") + .withPersonIdentifierFirstName("Maria") + .withPersonIdentifierLastName("Smith") + .withRelationshipType("Person") + .build(); + + Item author3 = ItemBuilder.createItem(context, col2) + .withTitle("Author3") + .withIssueDate("2016-02-13") + .withPersonIdentifierFirstName("Maybe") + .withPersonIdentifierLastName("Maybe") + .withRelationshipType("Person") + .build(); + + Item publication = ItemBuilder.createItem(context, col3) + .withTitle("Publication1") + .withIssueDate("2015-01-01") + .withRelationshipType("Publication") + .build(); + RelationshipType isAuthorOfPublicationRelationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), + "isAuthorOfPublication", "isPublicationOfAuthor"); + + String adminToken = getAuthToken(admin.getEmail(), password); + + // First create the structure of 5 metadatavalues just like the additions test. + context.restoreAuthSystemState(); + // This post request will add a first relationship to the publiction and thus create a first set of metadata + // For the author values, namely "Donald Smith" + MvcResult mvcResult = getClient(adminToken).perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author1.getID())) + .andExpect(status().isCreated()) + .andReturn(); + ObjectMapper mapper = new ObjectMapper(); + + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String firstRelationshipIdString = String.valueOf(map.get("id")); + + // This test will double check that the leftPlace for this relationship is indeed 0 + getClient(adminToken).perform(get("/api/core/relationships/" + firstRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("leftPlace", is(0))); + + + context.turnOffAuthorisationSystem(); + // We retrieve the publication again to ensure that we have the latest DB object of it + publication = itemService.find(context, publication.getID()); + // Add a plain text metadatavalue to the publication + // After this addition, the list of authors should like like "Donald Smith", "plain text" + itemService.addMetadata(context, publication, "dc", "contributor", "author", Item.ANY, "plain text"); + itemService.update(context, publication); + context.restoreAuthSystemState(); + List list = itemService.getMetadata(publication, "dc", "contributor", "author", Item.ANY); + + for (MetadataValue mdv : list) { + if (StringUtils.equals(mdv.getValue(), "plain text")) { + // Ensure that this is indeed the second metadatavalue in the list of authors for the publication + assertEquals(1, mdv.getPlace()); + } + } + + // This test checks again that the first relationship is still on leftplace 0 and not altered + // Because of the MetadataValue addition + getClient(adminToken).perform(get("/api/core/relationships/" + firstRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("leftPlace", is(0))); + + // Creates another Relationship for the Publication and thus adding a third metadata value for the author + mvcResult = getClient(adminToken).perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author2.getID())) + .andExpect(status().isCreated()) + .andReturn(); + + content = mvcResult.getResponse().getContentAsString(); + map = mapper.readValue(content, Map.class); + String secondRelationshipIdString = String.valueOf(map.get("id")); + + // This test verifies that the newly created Relationship is on leftPlace 2, because the first relationship + // is on leftPlace 0 and the plain text metadataValue occupies the place 1 + getClient(adminToken).perform(get("/api/core/relationships/" + secondRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("leftPlace", is(2))); + context.turnOffAuthorisationSystem(); + // Get the publication from the DB again to ensure that we have the latest object + publication = itemService.find(context, publication.getID()); + // Add a fourth metadata value to the publication + itemService.addMetadata(context, publication, "dc", "contributor", "author", Item.ANY, "plain text two"); + itemService.update(context, publication); + context.restoreAuthSystemState(); + list = itemService.getMetadata(publication, "dc", "contributor", "author", Item.ANY); + + for (MetadataValue mdv : list) { + if (StringUtils.equals(mdv.getValue(), "plain text two")) { + // Ensure that this plain text metadata value is on the fourth place (place 3) for the publication + assertEquals(3, mdv.getPlace()); + } + } + + // The list should currently look like this: "Donald Smith", "plain text", "Maria Smith", "plain text two" + + // This creates a third relationship for the publication and thus adding a fifth value for author metadatavalues + mvcResult = getClient(adminToken).perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author3.getID())) + .andExpect(status().isCreated()) + .andReturn(); + + content = mvcResult.getResponse().getContentAsString(); + map = mapper.readValue(content, Map.class); + String thirdRelationshipIdString = String.valueOf(map.get("id")); + + // This verifies that the newly created third relationship is on leftPlace 4. + getClient(adminToken).perform(get("/api/core/relationships/" + thirdRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("leftPlace", is(4))); + + context.turnOffAuthorisationSystem(); + publication = itemService.find(context, publication.getID()); + // Create another plain text metadata value on the publication + itemService.addMetadata(context, publication, "dc", "contributor", "author", Item.ANY, "plain text three"); + itemService.update(context, publication); + context.restoreAuthSystemState(); + list = itemService.getMetadata(publication, "dc", "contributor", "author", Item.ANY); + + for (MetadataValue mdv : list) { + if (StringUtils.equals(mdv.getValue(), "plain text three")) { + // Verify that this plain text value is indeed the 6th author in the list (place 5) + assertEquals(5, mdv.getPlace()); + } + } + + // Now we will have a dc.contributor.author metadatavalue list of size 6 in the following order: + // "Smith, Donald", "plain text", "Smith, Maria", "plain text two", "Maybe, Maybe", "plain text three" + + + // Now we delete the second relationship, the one that made "Smith, Maria" metadatavalue + // Ensure that all metadatavalues before this one in the list still hold the same place + // Ensure that all the metadatavalues after this one have their place lowered by one + getClient(adminToken).perform(delete("/api/core/relationships/" + secondRelationshipIdString)); + + getClient(adminToken).perform(get("/api/core/relationships/" + firstRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("leftPlace", is(0))); + + publication = itemService.find(context, publication.getID()); + list = itemService.getMetadata(publication, "dc", "contributor", "author", Item.ANY); + + for (MetadataValue mdv : list) { + if (StringUtils.equals(mdv.getValue(), "plain text")) { + assertEquals(1, mdv.getPlace()); + } + } + list = itemService.getMetadata(publication, "dc", "contributor", "author", Item.ANY); + + for (MetadataValue mdv : list) { + if (StringUtils.equals(mdv.getValue(), "plain text two")) { + assertEquals(2, mdv.getPlace()); + } + } + + + list = itemService.getMetadata(publication, "dc", "contributor", "author", Item.ANY); + + for (MetadataValue mdv : list) { + if (StringUtils.equals(mdv.getValue(), "plain text three")) { + assertEquals(4, mdv.getPlace()); + } + } + + + getClient(adminToken).perform(get("/api/core/relationships/" + thirdRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("leftPlace", is(3))); + + } + + /** + * This test will simply add Relationships between Items with a useForPlace attribute set to false for the + * RelationshipType. We want to test that the Relationships that are created will still have their place + * attributes handled in a correct way + * @throws Exception + */ + @Test + public void addRelationshipsNotUseForPlace() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + Collection col3 = CollectionBuilder.createCollection(context, child1).withName("OrgUnits").build(); + + Item author1 = ItemBuilder.createItem(context, col1) + .withTitle("Author1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .withRelationshipType("Person") + .build(); + + Item author2 = ItemBuilder.createItem(context, col2) + .withTitle("Author2") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria") + .withRelationshipType("Person") + .build(); + + Item author3 = ItemBuilder.createItem(context, col2) + .withTitle("Author3") + .withIssueDate("2016-02-13") + .withAuthor("Maybe, Maybe") + .withRelationshipType("Person") + .build(); + + Item orgUnit1 = ItemBuilder.createItem(context, col3) + .withTitle("OrgUnit1") + .withIssueDate("2015-01-01") + .withRelationshipType("OrgUnit") + .build(); + + RelationshipType isOrgUnitOfPersonRelationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Person"), + entityTypeService.findByEntityType(context, "OrgUnit"), + "isOrgUnitOfPerson", "isPersonOfOrgUnit"); + context.restoreAuthSystemState(); + String adminToken = getAuthToken(admin.getEmail(), password); + + // This is essentially a sequence of adding Relationships by POST and then checking with GET to see + // if the place is being set properly. + MvcResult mvcResult = getClient(adminToken).perform(post("/api/core/relationships") + .param("relationshipType", + isOrgUnitOfPersonRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + author1.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + orgUnit1.getID())) + .andExpect(status().isCreated()) + .andReturn(); + ObjectMapper mapper = new ObjectMapper(); + + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String firstRelationshipIdString = String.valueOf(map.get("id")); + + getClient(adminToken).perform(get("/api/core/relationships/" + firstRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("rightPlace", is(0))); + + mvcResult = getClient(adminToken).perform(post("/api/core/relationships") + .param("relationshipType", + isOrgUnitOfPersonRelationshipType.getID().toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + author2.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + orgUnit1.getID())) + .andExpect(status().isCreated()) + .andReturn(); + mapper = new ObjectMapper(); + + content = mvcResult.getResponse().getContentAsString(); + map = mapper.readValue(content, Map.class); + String secondRelationshipIdString = String.valueOf(map.get("id")); + + getClient(adminToken).perform(get("/api/core/relationships/" + secondRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("rightPlace", is(1))); + + mvcResult = getClient(adminToken).perform(post("/api/core/relationships") + .param("relationshipType", + isOrgUnitOfPersonRelationshipType.getID().toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + author3.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + orgUnit1.getID())) + .andExpect(status().isCreated()) + .andReturn(); + mapper = new ObjectMapper(); + + content = mvcResult.getResponse().getContentAsString(); + map = mapper.readValue(content, Map.class); + String thirdRelationshipIdString = String.valueOf(map.get("id")); + + getClient(adminToken).perform(get("/api/core/relationships/" + thirdRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("rightPlace", is(2))); + } + + /** + * This test will simply add Relationships between Items with a useForPlace attribute set to false for the + * RelationshipType. We want to test that the Relationships that are created will still have their place + * attributes handled in a correct way. It will then delete a Relationship and once again ensure that the place + * attributes are being handled correctly. + * @throws Exception + */ + @Test + public void addAndDeleteRelationshipsNotUseForPlace() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + Collection col3 = CollectionBuilder.createCollection(context, child1).withName("OrgUnits").build(); + + Item author1 = ItemBuilder.createItem(context, col1) + .withTitle("Author1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .withRelationshipType("Person") + .build(); + + Item author2 = ItemBuilder.createItem(context, col2) + .withTitle("Author2") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria") + .withRelationshipType("Person") + .build(); + + Item author3 = ItemBuilder.createItem(context, col2) + .withTitle("Author3") + .withIssueDate("2016-02-13") + .withAuthor("Maybe, Maybe") + .withRelationshipType("Person") + .build(); + + Item orgUnit1 = ItemBuilder.createItem(context, col3) + .withTitle("OrgUnit1") + .withIssueDate("2015-01-01") + .withRelationshipType("OrgUnit") + .build(); + + RelationshipType isOrgUnitOfPersonRelationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Person"), + entityTypeService.findByEntityType(context, "OrgUnit"), + "isOrgUnitOfPerson", "isPersonOfOrgUnit"); + context.restoreAuthSystemState(); + String adminToken = getAuthToken(admin.getEmail(), password); + + // This is essentially a sequence of adding Relationships by POST and then checking with GET to see + // if the place is being set properly. + MvcResult mvcResult = getClient(adminToken).perform(post("/api/core/relationships") + .param("relationshipType", + isOrgUnitOfPersonRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + author1.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + orgUnit1.getID())) + .andExpect(status().isCreated()) + .andReturn(); + ObjectMapper mapper = new ObjectMapper(); + + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String firstRelationshipIdString = String.valueOf(map.get("id")); + + getClient(adminToken).perform(get("/api/core/relationships/" + firstRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("rightPlace", is(0))); + + mvcResult = getClient(adminToken).perform(post("/api/core/relationships") + .param("relationshipType", + isOrgUnitOfPersonRelationshipType.getID().toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + author2.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + orgUnit1.getID())) + .andExpect(status().isCreated()) + .andReturn(); + mapper = new ObjectMapper(); + + content = mvcResult.getResponse().getContentAsString(); + map = mapper.readValue(content, Map.class); + String secondRelationshipIdString = String.valueOf(map.get("id")); + + getClient(adminToken).perform(get("/api/core/relationships/" + secondRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("rightPlace", is(1))); + + mvcResult = getClient(adminToken).perform(post("/api/core/relationships") + .param("relationshipType", + isOrgUnitOfPersonRelationshipType.getID().toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + author3.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + orgUnit1.getID())) + .andExpect(status().isCreated()) + .andReturn(); + mapper = new ObjectMapper(); + + content = mvcResult.getResponse().getContentAsString(); + map = mapper.readValue(content, Map.class); + String thirdRelationshipIdString = String.valueOf(map.get("id")); + + getClient(adminToken).perform(get("/api/core/relationships/" + thirdRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("rightPlace", is(2))); + + // Here we will delete the secondRelationship and then verify that the others have their place handled properly + getClient(adminToken).perform(delete("/api/core/relationships/" + secondRelationshipIdString)) + .andExpect(status().isNoContent()); + + getClient(adminToken).perform(get("/api/core/relationships/" + firstRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("rightPlace", is(0))); + + getClient(adminToken).perform(get("/api/core/relationships/" + thirdRelationshipIdString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("rightPlace", is(1))); + + } + + @Ignore + public void putRelationshipAdminAccess() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + Collection col3 = CollectionBuilder.createCollection(context, child1).withName("OrgUnits").build(); + + Item author1 = ItemBuilder.createItem(context, col1) + .withTitle("Author1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .withRelationshipType("Person") + .build(); + + Item author2 = ItemBuilder.createItem(context, col1) + .withTitle("Author2") + .withIssueDate("2017-10-12") + .withAuthor("Smith, Donalaze") + .withRelationshipType("Person") + .build(); + + Item publication = ItemBuilder.createItem(context, col3) + .withTitle("Publication1") + .withAuthor("Testy, TEst") + .withIssueDate("2015-01-01") + .withRelationshipType("Publication") + .build(); + + RelationshipType isAuthorOfPublicationRelationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), + "isAuthorOfPublication", "isPublicationOfAuthor"); + + + + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + + MvcResult mvcResult = getClient(token).perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author1.getID())) + .andExpect(status().isCreated()) + .andReturn(); + + ObjectMapper mapper = new ObjectMapper(); + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String id = String.valueOf(map.get("id")); + + MvcResult mvcResult2 = getClient(token).perform(put("/api/core/relationships/" + id) + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author2.getID())) + .andExpect(status().isOk()) + .andReturn(); + + getClient(token).perform(get("/api/core/relationships/" + id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.rightId", is(author2.getID().toString()))); + + } + + /** + * Create a relationship between publication 1 and author 1 + * Change it to a relationship between publication 1 and author 2 + * Verify this is possible for a user with WRITE permissions on author 1 and author 2 + */ + @Ignore + public void putRelationshipWriteAccessOnAuthors() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + Collection col3 = CollectionBuilder.createCollection(context, child1).withName("OrgUnits").build(); + + Item author1 = ItemBuilder.createItem(context, col1) + .withTitle("Author1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .withRelationshipType("Person") + .build(); + + Item author2 = ItemBuilder.createItem(context, col1) + .withTitle("Author2") + .withIssueDate("2017-10-12") + .withAuthor("Smith, Donalaze") + .withRelationshipType("Person") + .build(); + + Item publication = ItemBuilder.createItem(context, col3) + .withTitle("Publication1") + .withAuthor("Testy, TEst") + .withIssueDate("2015-01-01") + .withRelationshipType("Publication") + .build(); + + RelationshipType isAuthorOfPublicationRelationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), + "isAuthorOfPublication", "isPublicationOfAuthor"); + + + + EPerson user = EPersonBuilder.createEPerson(context) + .withNameInMetadata("first", "last") + .withEmail("testaze@gmail.com") + .withPassword(password) + .withLanguage(I18nUtil.getDefaultLocale().getLanguage()) + .build(); + context.setCurrentUser(user); + + authorizeService.addPolicy(context, author1, Constants.WRITE, user); + authorizeService.addPolicy(context, author2, Constants.WRITE, user); + + context.restoreAuthSystemState(); + + String token = getAuthToken(user.getEmail(), password); + + MvcResult mvcResult = getClient(token).perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType("text/uri-list")) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author1.getID())) + .andExpect(status().isCreated()) + .andReturn(); + + ObjectMapper mapper = new ObjectMapper(); + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String id = String.valueOf(map.get("id")); + + MvcResult mvcResult2 = getClient(token).perform(put("/api/core/relationships/" + id) + .contentType(MediaType.parseMediaType("text/uri-list")) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author2.getID())) + .andExpect(status().isOk()) + .andReturn(); + + getClient(token).perform(get("/api/core/relationships/" + id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.rightId", is(author2.getID().toString()))); + + } + + /** + * Create a relationship between publication 1 and author 1 + * Change it to a relationship between publication 1 and author 2 + * Verify this is possible for a user with WRITE permissions on publication 1 + */ + @Ignore + public void putRelationshipWriteAccessOnPublication() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + Collection col3 = CollectionBuilder.createCollection(context, child1).withName("OrgUnits").build(); + + Item author1 = ItemBuilder.createItem(context, col1) + .withTitle("Author1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .withRelationshipType("Person") + .build(); + + Item author2 = ItemBuilder.createItem(context, col1) + .withTitle("Author2") + .withIssueDate("2017-10-12") + .withAuthor("Smith, Donalaze") + .withRelationshipType("Person") + .build(); + + Item publication = ItemBuilder.createItem(context, col3) + .withTitle("Publication1") + .withAuthor("Testy, TEst") + .withIssueDate("2015-01-01") + .withRelationshipType("Publication") + .build(); + + RelationshipType isAuthorOfPublicationRelationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), + "isAuthorOfPublication", "isPublicationOfAuthor"); + + + + EPerson user = EPersonBuilder.createEPerson(context) + .withNameInMetadata("first", "last") + .withEmail("testaze@gmail.com") + .withPassword(password) + .withLanguage(I18nUtil.getDefaultLocale().getLanguage()) + .build(); + context.setCurrentUser(user); + + authorizeService.addPolicy(context, publication, Constants.WRITE, user); + + context.restoreAuthSystemState(); + + String token = getAuthToken(user.getEmail(), password); + + MvcResult mvcResult = getClient(token).perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType("text/uri-list")) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author1.getID())) + .andExpect(status().isCreated()) + .andReturn(); + + ObjectMapper mapper = new ObjectMapper(); + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String id = String.valueOf(map.get("id")); + + MvcResult mvcResult2 = getClient(token).perform(put("/api/core/relationships/" + id) + .contentType(MediaType.parseMediaType("text/uri-list")) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author2.getID())) + .andExpect(status().isOk()) + .andReturn(); + + getClient(token).perform(get("/api/core/relationships/" + id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.rightId", is(author2.getID().toString()))); + + } + + + /** + * Create a relationship between publication 1 and author 1 + * Change it to a relationship between publication 2 and author 1 + * Verify this is possible for a user with WRITE permissions on publication 1 and publication 2 + */ + @Ignore + public void putRelationshipWriteAccessOnPublications() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + Collection col3 = CollectionBuilder.createCollection(context, child1).withName("OrgUnits").build(); + + Item author1 = ItemBuilder.createItem(context, col1) + .withTitle("Author1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .withRelationshipType("Person") + .build(); + + Item author2 = ItemBuilder.createItem(context, col1) + .withTitle("Author2") + .withIssueDate("2017-10-12") + .withAuthor("Smith, Donalaze") + .withRelationshipType("Person") + .build(); + + Item publication1 = ItemBuilder.createItem(context, col3) + .withTitle("Publication1") + .withAuthor("Testy, TEst") + .withIssueDate("2015-01-01") + .withRelationshipType("Publication") + .build(); + Item publication2 = ItemBuilder.createItem(context, col3) + .withTitle("Publication2") + .withAuthor("Testy, TEstzeaze") + .withIssueDate("2015-01-01") + .withRelationshipType("Publication") + .build(); + + RelationshipType isAuthorOfPublicationRelationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), + "isAuthorOfPublication", "isPublicationOfAuthor"); + + + + EPerson user = EPersonBuilder.createEPerson(context) + .withNameInMetadata("first", "last") + .withEmail("testaze@gmail.com") + .withPassword(password) + .withLanguage(I18nUtil.getDefaultLocale().getLanguage()) + .build(); + context.setCurrentUser(user); + + authorizeService.addPolicy(context, publication1, Constants.WRITE, user); + authorizeService.addPolicy(context, publication2, Constants.WRITE, user); + + context.restoreAuthSystemState(); + + String token = getAuthToken(user.getEmail(), password); + + MvcResult mvcResult = getClient(token).perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType("text/uri-list")) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication1.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author1.getID())) + .andExpect(status().isCreated()) + .andReturn(); + + ObjectMapper mapper = new ObjectMapper(); + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String id = String.valueOf(map.get("id")); + + MvcResult mvcResult2 = getClient(token).perform(put("/api/core/relationships/" + id) + .contentType(MediaType.parseMediaType("text/uri-list")) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication2.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author1.getID())) + .andExpect(status().isOk()) + .andReturn(); + + getClient(token).perform(get("/api/core/relationships/" + id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.leftId", is(publication2.getID().toString()))); + + } + + + /** + * Create a relationship between publication 1 and author 1 + * Change it to a relationship between publication 2 and author 1 + * Verify this is possible for a user with WRITE permissions on author 1 + */ + @Ignore + public void putRelationshipWriteAccessOnAuthor() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + Collection col3 = CollectionBuilder.createCollection(context, child1).withName("OrgUnits").build(); + + Item author1 = ItemBuilder.createItem(context, col1) + .withTitle("Author1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .withRelationshipType("Person") + .build(); + + Item author2 = ItemBuilder.createItem(context, col1) + .withTitle("Author2") + .withIssueDate("2017-10-12") + .withAuthor("Smith, Donalaze") + .withRelationshipType("Person") + .build(); + + Item publication1 = ItemBuilder.createItem(context, col3) + .withTitle("Publication1") + .withAuthor("Testy, TEst") + .withIssueDate("2015-01-01") + .withRelationshipType("Publication") + .build(); + + Item publication2 = ItemBuilder.createItem(context, col3) + .withTitle("Publication2") + .withAuthor("Testy, TEstzea") + .withIssueDate("2015-01-01") + .withRelationshipType("Publication") + .build(); + + RelationshipType isAuthorOfPublicationRelationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), + "isAuthorOfPublication", "isPublicationOfAuthor"); + + + + EPerson user = EPersonBuilder.createEPerson(context) + .withNameInMetadata("first", "last") + .withEmail("testaze@gmail.com") + .withPassword(password) + .withLanguage(I18nUtil.getDefaultLocale().getLanguage()) + .build(); + context.setCurrentUser(user); + + authorizeService.addPolicy(context, author1, Constants.WRITE, user); + + context.restoreAuthSystemState(); + + String token = getAuthToken(user.getEmail(), password); + + MvcResult mvcResult = getClient(getAuthToken(admin.getEmail(), password)) + .perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType("text/uri-list")) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication1.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author1.getID())) + .andExpect(status().isCreated()) + .andReturn(); + + ObjectMapper mapper = new ObjectMapper(); + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String id = String.valueOf(map.get("id")); + + MvcResult mvcResult2 = getClient(token).perform(put("/api/core/relationships/" + id) + .contentType(MediaType.parseMediaType("text/uri-list")) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication2.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author1.getID())) + .andExpect(status().isOk()) + .andReturn(); + + getClient(token).perform(get("/api/core/relationships/" + id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.leftId", is(publication2.getID().toString()))); + + } + + + /** + * Create a relationship between publication 1 and author 1 + * Change it to a relationship between publication 1 and author 2 + * Verify this is NOT possible for a user without WRITE permissions + */ + @Ignore + public void putRelationshipNoAccess() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + Collection col3 = CollectionBuilder.createCollection(context, child1).withName("OrgUnits").build(); + + Item author1 = ItemBuilder.createItem(context, col1) + .withTitle("Author1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .withRelationshipType("Person") + .build(); + + Item author2 = ItemBuilder.createItem(context, col1) + .withTitle("Author2") + .withIssueDate("2017-10-12") + .withAuthor("Smith, Donalaze") + .withRelationshipType("Person") + .build(); + + Item publication = ItemBuilder.createItem(context, col3) + .withTitle("Publication1") + .withAuthor("Testy, TEst") + .withIssueDate("2015-01-01") + .withRelationshipType("Publication") + .build(); + + RelationshipType isAuthorOfPublicationRelationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), + "isAuthorOfPublication", "isPublicationOfAuthor"); + + + + EPerson user = EPersonBuilder.createEPerson(context) + .withNameInMetadata("first", "last") + .withEmail("testaze@gmail.com") + .withPassword(password) + .withLanguage(I18nUtil.getDefaultLocale().getLanguage()) + .build(); + context.setCurrentUser(user); + + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + + MvcResult mvcResult = getClient(token).perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType("text/uri-list")) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author1.getID())) + .andExpect(status().isCreated()) + .andReturn(); + + ObjectMapper mapper = new ObjectMapper(); + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String id = String.valueOf(map.get("id")); + token = getAuthToken(user.getEmail(), password); + + MvcResult mvcResult2 = getClient(token).perform(put("/api/core/relationships/" + id) + .contentType(MediaType.parseMediaType("text/uri-list")) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author2.getID())) + .andExpect(status().isForbidden()) + .andReturn(); + + } + + /** + * Create a relationship between publication 1 and author 1 + * Change it to a relationship between publication 1 and author 2 + * Verify this is NOT possible for a user with WRITE permissions on author 1 + */ + @Ignore + public void putRelationshipOnlyAccessOnOneAuthor() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + Collection col3 = CollectionBuilder.createCollection(context, child1).withName("OrgUnits").build(); + + Item author1 = ItemBuilder.createItem(context, col1) + .withTitle("Author1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .withRelationshipType("Person") + .build(); + + Item author2 = ItemBuilder.createItem(context, col1) + .withTitle("Author2") + .withIssueDate("2017-10-12") + .withAuthor("Smith, Donalaze") + .withRelationshipType("Person") + .build(); + + Item publication = ItemBuilder.createItem(context, col3) + .withTitle("Publication1") + .withAuthor("Testy, TEst") + .withIssueDate("2015-01-01") + .withRelationshipType("Publication") + .build(); + + RelationshipType isAuthorOfPublicationRelationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), + "isAuthorOfPublication", "isPublicationOfAuthor"); + + + + EPerson user = EPersonBuilder.createEPerson(context) + .withNameInMetadata("first", "last") + .withEmail("testaze@gmail.com") + .withPassword(password) + .withLanguage(I18nUtil.getDefaultLocale().getLanguage()) + .build(); + context.setCurrentUser(user); + + authorizeService.addPolicy(context, author1, Constants.WRITE, user); + + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + + MvcResult mvcResult = getClient(token).perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType("text/uri-list")) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author1.getID())) + .andExpect(status().isCreated()) + .andReturn(); + + ObjectMapper mapper = new ObjectMapper(); + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String id = String.valueOf(map.get("id")); + token = getAuthToken(user.getEmail(), password); + + MvcResult mvcResult2 = getClient(token).perform(put("/api/core/relationships/" + id) + .contentType(MediaType.parseMediaType("text/uri-list")) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author2.getID())) + .andExpect(status().isForbidden()) + .andReturn(); + + } + + /** + * Create a relationship between publication 1 and author 1 + * Change it to a relationship between publication 2 and author 1 + * Verify this is NOT possible for a user with WRITE permissions on publication 1 + */ + @Ignore + public void putRelationshipOnlyAccessOnOnePublication() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + Collection col3 = CollectionBuilder.createCollection(context, child1).withName("OrgUnits").build(); + + Item author1 = ItemBuilder.createItem(context, col1) + .withTitle("Author1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .withRelationshipType("Person") + .build(); + + Item author2 = ItemBuilder.createItem(context, col1) + .withTitle("Author2") + .withIssueDate("2017-10-12") + .withAuthor("Smith, Donalaze") + .withRelationshipType("Person") + .build(); + + Item publication1 = ItemBuilder.createItem(context, col3) + .withTitle("Publication1") + .withAuthor("Testy, TEst") + .withIssueDate("2015-01-01") + .withRelationshipType("Publication") + .build(); + Item publication2 = ItemBuilder.createItem(context, col3) + .withTitle("Publication2") + .withAuthor("Testy, TEstzeaze") + .withIssueDate("2015-01-01") + .withRelationshipType("Publication") + .build(); + + RelationshipType isAuthorOfPublicationRelationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), + "isAuthorOfPublication", "isPublicationOfAuthor"); + + + + EPerson user = EPersonBuilder.createEPerson(context) + .withNameInMetadata("first", "last") + .withEmail("testaze@gmail.com") + .withPassword(password) + .withLanguage(I18nUtil.getDefaultLocale().getLanguage()) + .build(); + context.setCurrentUser(user); + + authorizeService.addPolicy(context, publication1, Constants.WRITE, user); + + String token = getAuthToken(user.getEmail(), password); + + context.restoreAuthSystemState(); + + MvcResult mvcResult = getClient(token).perform(post("/api/core/relationships") + .param("relationshipType", + isAuthorOfPublicationRelationshipType.getID() + .toString()) + .contentType(MediaType.parseMediaType("text/uri-list")) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication1.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author1.getID())) + .andExpect(status().isCreated()) + .andReturn(); + + ObjectMapper mapper = new ObjectMapper(); + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String id = String.valueOf(map.get("id")); + + MvcResult mvcResult2 = getClient(token).perform(put("/api/core/relationships/" + id) + .contentType(MediaType.parseMediaType("text/uri-list")) + .content( + "https://localhost:8080/spring-rest/api/core/items/" + publication2.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + author1.getID())) + .andExpect(status().isForbidden()) + .andReturn(); + + getClient(token).perform(get("/api/core/relationships/" + id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.leftId", is(publication1.getID().toString()))); + + } +} diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/RelationshipTypeRestControllerIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/RelationshipTypeRestControllerIT.java new file mode 100644 index 0000000000..4f2c307ac8 --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/RelationshipTypeRestControllerIT.java @@ -0,0 +1,210 @@ +/** + * 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.containsInAnyOrder; +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 org.dspace.app.rest.builder.CollectionBuilder; +import org.dspace.app.rest.builder.CommunityBuilder; +import org.dspace.app.rest.builder.ItemBuilder; +import org.dspace.app.rest.builder.RelationshipBuilder; +import org.dspace.app.rest.matcher.EntityTypeMatcher; +import org.dspace.app.rest.matcher.PageMatcher; +import org.dspace.app.rest.matcher.RelationshipMatcher; +import org.dspace.app.rest.matcher.RelationshipTypeMatcher; +import org.dspace.app.rest.test.AbstractEntityIntegrationTest; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.EntityType; +import org.dspace.content.Item; +import org.dspace.content.Relationship; +import org.dspace.content.RelationshipType; +import org.dspace.content.service.EntityTypeService; +import org.dspace.content.service.RelationshipTypeService; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +public class RelationshipTypeRestControllerIT extends AbstractEntityIntegrationTest { + + @Autowired + private RelationshipTypeService relationshipTypeService; + + @Autowired + private EntityTypeService entityTypeService; + + @Test + public void findAllEntityTypes() throws Exception { + + getClient().perform(get("/api/core/entitytypes")) + + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page", + is(PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 7)))) + .andExpect(jsonPath("$._embedded.entitytypes", containsInAnyOrder( + EntityTypeMatcher.matchEntityTypeEntryForLabel("Publication"), + EntityTypeMatcher.matchEntityTypeEntryForLabel("Person"), + EntityTypeMatcher.matchEntityTypeEntryForLabel("Project"), + EntityTypeMatcher.matchEntityTypeEntryForLabel("OrgUnit"), + EntityTypeMatcher.matchEntityTypeEntryForLabel("Journal"), + EntityTypeMatcher.matchEntityTypeEntryForLabel("JournalVolume"), + EntityTypeMatcher.matchEntityTypeEntryForLabel("JournalIssue") + + ))) + ; + } + + @Test + public void findAllRelationshipTypesForPublications() throws Exception { + + EntityType publicationEntityType = entityTypeService.findByEntityType(context, "Publication"); + EntityType personEntityType = entityTypeService.findByEntityType(context, "Person"); + EntityType projectEntityType = entityTypeService.findByEntityType(context, "Project"); + EntityType orgunitEntityType = entityTypeService.findByEntityType(context, "OrgUnit"); + EntityType journalIssueEntityType = entityTypeService.findByEntityType(context, "journalIssue"); + + RelationshipType relationshipType1 = relationshipTypeService + .findbyTypesAndLabels(context, publicationEntityType, personEntityType, "isAuthorOfPublication", + "isPublicationOfAuthor"); + RelationshipType relationshipType2 = relationshipTypeService + .findbyTypesAndLabels(context, publicationEntityType, projectEntityType, "isProjectOfPublication", + "isPublicationOfProject"); + RelationshipType relationshipType3 = relationshipTypeService + .findbyTypesAndLabels(context, publicationEntityType, orgunitEntityType, "isOrgUnitOfPublication", + "isPublicationOfOrgUnit"); + RelationshipType relationshipType4 = relationshipTypeService + .findbyTypesAndLabels(context, journalIssueEntityType, publicationEntityType, "isPublicationOfJournalIssue", + "isJournalIssueOfPublication"); + RelationshipType relationshipType5 = relationshipTypeService + .findbyTypesAndLabels(context, publicationEntityType, orgunitEntityType, "isAuthorOfPublication", + "isPublicationOfAuthor"); + getClient().perform(get("/api/core/entitytypes/" + publicationEntityType.getID() + "/relationshiptypes")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.relationshiptypes", containsInAnyOrder( + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType1), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType2), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType3), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType4), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType5) + ))); + } + + @Test + public void createAndFindRelationships() throws Exception { + + context.turnOffAuthorisationSystem(); + + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + Collection col3 = CollectionBuilder.createCollection(context, child1).withName("OrgUnits").build(); + + Item author1 = ItemBuilder.createItem(context, col1) + .withTitle("Author1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .withRelationshipType("Person") + .build(); + + Item author2 = ItemBuilder.createItem(context, col2) + .withTitle("Author2") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria") + .withRelationshipType("Person") + .build(); + + Item author3 = ItemBuilder.createItem(context, col2) + .withTitle("Author3") + .withIssueDate("2016-02-13") + .withAuthor("Maybe, Maybe") + .withRelationshipType("Person") + .build(); + + Item orgUnit1 = ItemBuilder.createItem(context, col3) + .withTitle("OrgUnit1") + .withAuthor("Testy, TEst") + .withIssueDate("2015-01-01") + .withRelationshipType("OrgUnit") + .build(); + + Item project1 = ItemBuilder.createItem(context, col3) + .withTitle("Project1") + .withAuthor("Testy, TEst") + .withIssueDate("2015-01-01") + .withRelationshipType("Project") + .build(); + + Item publication = ItemBuilder.createItem(context, col3) + .withTitle("Publication1") + .withAuthor("Testy, TEst") + .withIssueDate("2015-01-01") + .withRelationshipType("Publication") + .build(); + + Item publication2 = ItemBuilder.createItem(context, col3) + .withTitle("Publication2") + .withIssueDate("2015-01-01") + .withRelationshipType("Publication") + .build(); + + RelationshipType isOrgUnitOfPersonRelationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Person"), + entityTypeService.findByEntityType(context, "OrgUnit"), + "isOrgUnitOfPerson", "isPersonOfOrgUnit"); + RelationshipType isOrgUnitOfProjectRelationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Project"), + entityTypeService.findByEntityType(context, "OrgUnit"), + "isOrgUnitOfProject", "isProjectOfOrgUnit"); + RelationshipType isAuthorOfPublicationRelationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), + "isAuthorOfPublication", "isPublicationOfAuthor"); + + Relationship relationship1 = RelationshipBuilder + .createRelationshipBuilder(context, publication, author1, isAuthorOfPublicationRelationshipType).build(); + Relationship relationship2 = RelationshipBuilder + .createRelationshipBuilder(context, publication, author2, isAuthorOfPublicationRelationshipType).build(); + Relationship relationship3 = RelationshipBuilder + .createRelationshipBuilder(context, publication2, author2, isAuthorOfPublicationRelationshipType).build(); + Relationship relationship4 = RelationshipBuilder + .createRelationshipBuilder(context, publication2, author3, isAuthorOfPublicationRelationshipType).build(); + + context.restoreAuthSystemState(); + getClient().perform(get("/api/core/relationships/isAuthorOfPublication")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.relationships", containsInAnyOrder( + RelationshipMatcher.matchRelationship(relationship1), + RelationshipMatcher.matchRelationship(relationship2), + RelationshipMatcher.matchRelationship(relationship3), + RelationshipMatcher.matchRelationship(relationship4) + ))); + + getClient().perform(get("/api/core/relationships/isAuthorOfPublication?dso=" + publication.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.relationships", containsInAnyOrder( + RelationshipMatcher.matchRelationship(relationship1), + RelationshipMatcher.matchRelationship(relationship2) + ))); + + getClient().perform(get("/api/core/relationships/isAuthorOfPublication?dso=" + publication2.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.relationships", containsInAnyOrder( + RelationshipMatcher.matchRelationship(relationship3), + RelationshipMatcher.matchRelationship(relationship4) + ))); + } +} diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/RelationshipTypeRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/RelationshipTypeRestRepositoryIT.java new file mode 100644 index 0000000000..6539451aef --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/RelationshipTypeRestRepositoryIT.java @@ -0,0 +1,254 @@ + +/** + * 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.CoreMatchers.containsString; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.isEmptyOrNullString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +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.sql.SQLException; +import java.util.List; + +import org.dspace.app.rest.matcher.EntityTypeMatcher; +import org.dspace.app.rest.matcher.RelationshipTypeMatcher; +import org.dspace.app.rest.test.AbstractEntityIntegrationTest; +import org.dspace.content.RelationshipType; +import org.dspace.content.service.EntityTypeService; +import org.dspace.content.service.RelationshipTypeService; +import org.h2.util.StringUtils; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +public class RelationshipTypeRestRepositoryIT extends AbstractEntityIntegrationTest { + + @Autowired + private RelationshipTypeService relationshipTypeService; + + @Autowired + private EntityTypeService entityTypeService; + + @Test + public void findAllRelationshipTypesTest() throws SQLException { + assertEquals(10, relationshipTypeService.findAll(context).size()); + } + + @Test + public void findPublicationPersonRelationshipType() throws SQLException { + String leftTypeString = "Publication"; + String rightTypeString = "Person"; + String leftLabel = "isAuthorOfPublication"; + String rightLabel = "isPublicationOfAuthor"; + checkRelationshipType(leftTypeString, rightTypeString, leftLabel, rightLabel); + } + + @Test + public void findPublicationProjectRelationshipType() throws SQLException { + String leftTypeString = "Publication"; + String rightTypeString = "Project"; + String leftLabel = "isProjectOfPublication"; + String rightLabel = "isPublicationOfProject"; + checkRelationshipType(leftTypeString, rightTypeString, leftLabel, rightLabel); + } + + @Test + public void findPublicationOrgUnitRelationshipType() throws SQLException { + String leftTypeString = "Publication"; + String rightTypeString = "OrgUnit"; + String leftLabel = "isOrgUnitOfPublication"; + String rightLabel = "isPublicationOfOrgUnit"; + checkRelationshipType(leftTypeString, rightTypeString, leftLabel, rightLabel); + } + + @Test + public void findPersonProjectRelationshipType() throws SQLException { + String leftTypeString = "Person"; + String rightTypeString = "Project"; + String leftLabel = "isProjectOfPerson"; + String rightLabel = "isPersonOfProject"; + checkRelationshipType(leftTypeString, rightTypeString, leftLabel, rightLabel); + } + + @Test + public void findPersonOrgUnitRelationshipType() throws SQLException { + String leftTypeString = "Person"; + String rightTypeString = "OrgUnit"; + String leftLabel = "isOrgUnitOfPerson"; + String rightLabel = "isPersonOfOrgUnit"; + checkRelationshipType(leftTypeString, rightTypeString, leftLabel, rightLabel); + } + + @Test + public void findProjectOrgUnitRelationshipType() throws SQLException { + String leftTypeString = "Project"; + String rightTypeString = "OrgUnit"; + String leftLabel = "isOrgUnitOfProject"; + String rightLabel = "isProjectOfOrgUnit"; + checkRelationshipType(leftTypeString, rightTypeString, leftLabel, rightLabel); + } + + @Test + public void findJournalJournalVolumeRelationshipType() throws SQLException { + String leftTypeString = "Journal"; + String rightTypeString = "JournalVolume"; + String leftLabel = "isVolumeOfJournal"; + String rightLabel = "isJournalOfVolume"; + checkRelationshipType(leftTypeString, rightTypeString, leftLabel, rightLabel); + } + + @Test + public void findJournalVolumeJournalIssueRelationshipType() throws SQLException { + String leftTypeString = "JournalVolume"; + String rightTypeString = "JournalIssue"; + String leftLabel = "isIssueOfJournalVolume"; + String rightLabel = "isJournalVolumeOfIssue"; + checkRelationshipType(leftTypeString, rightTypeString, leftLabel, rightLabel); + } + + private void checkRelationshipType(String leftType, String rightType, String leftLabel, String rightLabel) + throws SQLException { + RelationshipType relationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, leftType), + entityTypeService.findByEntityType(context, rightType), + leftLabel, rightLabel); + assertNotNull(relationshipType); + assertEquals(entityTypeService.findByEntityType(context, leftType), + relationshipType.getLeftType()); + assertEquals(entityTypeService.findByEntityType(context, rightType), + relationshipType.getRightType()); + assertEquals(leftLabel, relationshipType.getLeftLabel()); + assertEquals(rightLabel, relationshipType.getRightLabel()); + } + + @Test + public void getAllRelationshipTypesEndpointTest() throws Exception { + //When we call this facets endpoint + List relationshipTypes = relationshipTypeService.findAll(context); + + getClient().perform(get("/api/core/relationshiptypes")) + + //We expect a 200 OK status + .andExpect(status().isOk()) + //The type has to be 'discover' + .andExpect(jsonPath("$.page.totalElements", is(10))) + //There needs to be a self link to this endpoint + .andExpect(jsonPath("$._links.self.href", containsString("api/core/relationshiptypes"))) + //We have 4 facets in the default configuration, they need to all be present in the embedded section + .andExpect(jsonPath("$._embedded.relationshiptypes", containsInAnyOrder( + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(0)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(1)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(2)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(3)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(4)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(5)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(6)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(7)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(8)), + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipTypes.get(9))) + )); + } + + @Test + public void entityTypeForPublicationPersonRelationshipTypeTest() throws Exception { + + List relationshipTypes = relationshipTypeService.findAll(context); + + RelationshipType foundRelationshipType = null; + for (RelationshipType relationshipType : relationshipTypes) { + if (StringUtils.equals(relationshipType.getLeftLabel(), "isAuthorOfPublication") && StringUtils + .equals(relationshipType.getRightLabel(), "isPublicationOfAuthor")) { + foundRelationshipType = relationshipType; + break; + } + } + + if (foundRelationshipType != null) { + getClient().perform(get("/api/core/relationshiptypes/" + foundRelationshipType.getID())) + .andExpect(jsonPath("$._embedded.leftType", + EntityTypeMatcher.matchEntityTypeEntryForLabel("Publication"))) + .andExpect( + jsonPath("$._embedded.rightType", EntityTypeMatcher.matchEntityTypeEntryForLabel("Person"))); + } else { + throw new Exception("RelationshipType not found for isIssueOfJournalVolume"); + } + + } + + @Test + public void cardinalityOnAuthorPublicationRelationshipTypesTest() throws Exception { + RelationshipType relationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), "isAuthorOfPublication", + "isPublicationOfAuthor"); + assertEquals(((Integer) 0), relationshipType.getLeftMinCardinality()); + assertEquals(((Integer) 0), relationshipType.getRightMinCardinality()); + assertNull(relationshipType.getLeftMaxCardinality()); + assertNull(null, relationshipType.getRightMaxCardinality()); + + getClient().perform(get("/api/core/relationshiptypes/" + relationshipType.getID())) + .andExpect(jsonPath("$.leftMinCardinality", is(0))) + .andExpect(jsonPath("$.rightMinCardinality", is(0))) + .andExpect(jsonPath("$.leftMaxCardinality", isEmptyOrNullString())) + .andExpect(jsonPath("$.rightMaxCardinality", isEmptyOrNullString())); + + } + + @Test + public void entityTypeForIssueJournalRelationshipTypeTest() throws Exception { + + List relationshipTypes = relationshipTypeService.findAll(context); + + RelationshipType foundRelationshipType = null; + for (RelationshipType relationshipType : relationshipTypes) { + if (StringUtils.equals(relationshipType.getLeftLabel(), "isIssueOfJournalVolume") && StringUtils + .equals(relationshipType.getRightLabel(), "isJournalVolumeOfIssue")) { + foundRelationshipType = relationshipType; + break; + } + } + + if (foundRelationshipType != null) { + getClient().perform(get("/api/core/relationshiptypes/" + foundRelationshipType.getID())) + .andExpect(jsonPath("$._embedded.leftType", + EntityTypeMatcher.matchEntityTypeEntryForLabel("JournalVolume"))) + .andExpect(jsonPath("$._embedded.rightType", + EntityTypeMatcher.matchEntityTypeEntryForLabel("JournalIssue"))); + } else { + throw new Exception("RelationshipType not found for isIssueOfJournalVolume"); + } + + } + + @Test + public void cardinalityOnIssueJournalJournalVolumeRelationshipTypesTest() throws Exception { + RelationshipType relationshipType = relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "JournalVolume"), + entityTypeService.findByEntityType(context, "JournalIssue"), "isIssueOfJournalVolume", + "isJournalVolumeOfIssue"); + assertEquals(((Integer) 0), relationshipType.getLeftMinCardinality()); + assertEquals(((Integer) 1), relationshipType.getRightMinCardinality()); + assertNull(relationshipType.getLeftMaxCardinality()); + assertEquals(((Integer) 1), relationshipType.getRightMaxCardinality()); + + getClient().perform(get("/api/core/relationshiptypes/" + relationshipType.getID())) + .andExpect(jsonPath("$.leftMinCardinality", is(0))) + .andExpect(jsonPath("$.rightMinCardinality", is(1))) + .andExpect(jsonPath("$.leftMaxCardinality", isEmptyOrNullString())) + .andExpect(jsonPath("$.rightMaxCardinality", is(1))); + + } + + +} diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/UriListParsingIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/UriListParsingIT.java new file mode 100644 index 0000000000..33aa52dfec --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/UriListParsingIT.java @@ -0,0 +1,88 @@ +/** + * 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.core.IsEqual.equalTo; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import org.dspace.app.rest.builder.CollectionBuilder; +import org.dspace.app.rest.builder.CommunityBuilder; +import org.dspace.app.rest.builder.ItemBuilder; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.rest.utils.Utils; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.core.Constants; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mock.web.MockHttpServletRequest; + +/** + * This test class will test the UriList parsing method in the Utils class + */ +public class UriListParsingIT extends AbstractControllerIntegrationTest { + + @Autowired + protected Utils utils; + + @Test + public void mockRequestTextUriParsingTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + + //2. Three public items that are readable by Anonymous with different subjects + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + Item publicItem2 = ItemBuilder.createItem(context, col2) + .withTitle("Public item 2") + .withIssueDate("2016-02-13") + .withAuthor("Smith, Maria").withAuthor("Doe, Jane") + .withSubject("TestingForMore").withSubject("ExtraEntry") + .build(); + + context.restoreAuthSystemState(); + + + MockHttpServletRequest mockRequest = new MockHttpServletRequest(); + String uriListString = "https://localhost:8080/spring-rest/api/core/items/" + publicItem1.getID() + "\n" + + "https://localhost:8080/spring-rest/api/core/items/" + publicItem2.getID(); + mockRequest.setContentType("text/uri-list"); + mockRequest.setContent(uriListString.getBytes()); + List dSpaceObjectList = utils.constructDSpaceObjectList( + ContextUtil.obtainContext(mockRequest), utils.getStringListFromRequest(mockRequest)); + assertThat("DSpaceObject List is of size 2" ,dSpaceObjectList.size(), equalTo(2)); + assertThat("DSpaceObject 1 is an item", dSpaceObjectList.get(0).getType(), equalTo(Constants.ITEM)); + assertThat("DSpaceObject 2 is an item", dSpaceObjectList.get(1).getType(), equalTo(Constants.ITEM)); + assertTrue(dSpaceObjectList.get(0).equals(publicItem1)); + assertTrue(dSpaceObjectList.get(1).equals(publicItem2)); + + + } +} diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index 92c28f78d9..b797e489c5 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java @@ -445,19 +445,22 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration // create a workspaceitem explicitly in the col1 getClient(authToken).perform(post("/api/submission/workspaceitems") - .param("collection", col1.getID().toString())) + .param("collection", col1.getID().toString()) + .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andExpect(jsonPath("$._embedded.collection.id", is(col1.getID().toString()))); // create a workspaceitem explicitly in the col2 getClient(authToken).perform(post("/api/submission/workspaceitems") - .param("collection", col2.getID().toString())) + .param("collection", col2.getID().toString()) + .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andExpect(jsonPath("$._embedded.collection.id", is(col2.getID().toString()))); // create a workspaceitem without an explicit collection, this will go in the first valid collection for the // user: the col1 - getClient(authToken).perform(post("/api/submission/workspaceitems")) + getClient(authToken).perform(post("/api/submission/workspaceitems") + .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) .andExpect(jsonPath("$._embedded.collection.id", is(col1.getID().toString()))); @@ -628,7 +631,8 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration // create an empty workspaceitem explicitly in the col1, check validation on creation getClient(authToken).perform(post("/api/submission/workspaceitems") - .param("collection", col1.getID().toString())) + .param("collection", col1.getID().toString()) + .contentType(org.springframework.http.MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) // title and dateissued are required in the first panel // the json path with a @ selector always return an array diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/AbstractBuilder.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/AbstractBuilder.java index 095b7c702b..96d0c48c72 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/AbstractBuilder.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/AbstractBuilder.java @@ -7,12 +7,11 @@ */ package org.dspace.app.rest.builder; -import java.util.Comparator; -import java.util.LinkedList; import java.util.List; import org.apache.commons.collections4.CollectionUtils; import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.builder.util.AbstractBuilderCleanupUtil; import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.ResourcePolicyService; @@ -23,10 +22,13 @@ import org.dspace.content.service.BitstreamService; import org.dspace.content.service.BundleService; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; +import org.dspace.content.service.EntityTypeService; import org.dspace.content.service.InstallItemService; import org.dspace.content.service.ItemService; import org.dspace.content.service.MetadataFieldService; import org.dspace.content.service.MetadataSchemaService; +import org.dspace.content.service.RelationshipService; +import org.dspace.content.service.RelationshipTypeService; import org.dspace.content.service.SiteService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Context; @@ -77,10 +79,17 @@ public abstract class AbstractBuilder { static MetadataFieldService metadataFieldService; static MetadataSchemaService metadataSchemaService; static SiteService siteService; + static RelationshipService relationshipService; + static RelationshipTypeService relationshipTypeService; + static EntityTypeService entityTypeService; protected Context context; - private static List builders = new LinkedList<>(); + /** + * This static class will make sure that the objects built with the builders are disposed of in a foreign-key + * constraint safe manner by predefining an order + */ + private static AbstractBuilderCleanupUtil abstractBuilderCleanupUtil = new AbstractBuilderCleanupUtil(); /** * log4j category */ @@ -88,7 +97,8 @@ public abstract class AbstractBuilder { protected AbstractBuilder(Context context) { this.context = context; - builders.add(this); + //Register this specific builder to be deleted later on + abstractBuilderCleanupUtil.addToMap(this); } public static void init() { @@ -114,6 +124,9 @@ public abstract class AbstractBuilder { metadataFieldService = ContentServiceFactory.getInstance().getMetadataFieldService(); metadataSchemaService = ContentServiceFactory.getInstance().getMetadataSchemaService(); siteService = ContentServiceFactory.getInstance().getSiteService(); + relationshipService = ContentServiceFactory.getInstance().getRelationshipService(); + relationshipTypeService = ContentServiceFactory.getInstance().getRelationshipTypeService(); + entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService(); // Temporarily disabled claimedTaskService = XmlWorkflowServiceFactory.getInstance().getClaimedTaskService(); @@ -146,19 +159,15 @@ public abstract class AbstractBuilder { metadataFieldService = null; metadataSchemaService = null; siteService = null; + relationshipService = null; + relationshipTypeService = null; + entityTypeService = null; } public static void cleanupObjects() throws Exception { - builders.sort(new Comparator() { - @Override - public int compare(AbstractBuilder o1, AbstractBuilder o2) { - // we want descending order, hight priority first - return o2.getPriority() - o1.getPriority(); - } - }); - for (AbstractBuilder builder : builders) { - builder.cleanup(); - } + + // This call will make sure that the map with AbstractBuilders will be cleaned up + abstractBuilderCleanupUtil.cleanupBuilders(); // Bitstreams still leave a trace when deleted, so we need to fully "expunge" them try (Context c = new Context()) { @@ -176,16 +185,10 @@ public abstract class AbstractBuilder { } /** - * Return the priority to give to the builder during the cleanup phase. It MUST be a positive integer. High values - * mean that the builder will be invoked soon during the cleanup phase - * - * @return + * This method will ensure that the DSpaceObject contained within the Builder will be cleaned up properly + * @throws Exception If something goes wrong */ - protected int getPriority() { - return 100; - } - - protected abstract void cleanup() throws Exception; + public abstract void cleanup() throws Exception; public abstract T build(); diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/AbstractDSpaceObjectBuilder.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/AbstractDSpaceObjectBuilder.java index f74d09adfc..b61cb041f2 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/AbstractDSpaceObjectBuilder.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/AbstractDSpaceObjectBuilder.java @@ -41,7 +41,7 @@ public abstract class AbstractDSpaceObjectBuilder this.context = context; } - protected abstract void cleanup() throws Exception; + public abstract void cleanup() throws Exception; protected abstract DSpaceObjectService getService(); diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/BitstreamBuilder.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/BitstreamBuilder.java index 080a6d68c3..f472ae987b 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/BitstreamBuilder.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/BitstreamBuilder.java @@ -120,7 +120,8 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder { return bitstream; } - protected void cleanup() throws Exception { + @Override + public void cleanup() throws Exception { delete(bitstream); } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/BitstreamFormatBuilder.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/BitstreamFormatBuilder.java index b0d169bfb8..5635386ae2 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/BitstreamFormatBuilder.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/BitstreamFormatBuilder.java @@ -31,7 +31,7 @@ public class BitstreamFormatBuilder extends AbstractCRUDBuilder } @Override - protected void cleanup() throws Exception { + public void cleanup() throws Exception { delete(bitstreamFormat); } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/ClaimedTaskBuilder.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/ClaimedTaskBuilder.java index 8a15caac24..81e5552f5e 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/ClaimedTaskBuilder.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/ClaimedTaskBuilder.java @@ -14,7 +14,7 @@ import org.dspace.content.Collection; import org.dspace.content.DCDate; import org.dspace.content.Item; import org.dspace.content.LicenseUtils; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.WorkspaceItem; import org.dspace.core.Constants; import org.dspace.core.Context; @@ -138,7 +138,7 @@ public class ClaimedTaskBuilder extends AbstractBuilder { } public CollectionBuilder withName(final String name) { - return setMetadataSingleValue(collection, MetadataSchema.DC_SCHEMA, "title", null, name); + return setMetadataSingleValue(collection, MetadataSchemaEnum.DC.getName(), "title", null, name); } public CollectionBuilder withLogo(final String content) throws AuthorizeException, IOException, SQLException { @@ -111,7 +111,8 @@ public class CollectionBuilder extends AbstractDSpaceObjectBuilder { return collection; } - protected void cleanup() throws Exception { + @Override + public void cleanup() throws Exception { deleteWorkflowGroups(collection); delete(collection); } @@ -140,12 +141,4 @@ public class CollectionBuilder extends AbstractDSpaceObjectBuilder { protected DSpaceObjectService getService() { return collectionService; } - - @Override - /** - * Collection must be deleted before community - */ - protected int getPriority() { - return 150; - } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/CommunityBuilder.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/CommunityBuilder.java index 1cf9ba74cb..befa1daad3 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/CommunityBuilder.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/CommunityBuilder.java @@ -10,12 +10,13 @@ package org.dspace.app.rest.builder; import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; +import java.util.UUID; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.CharEncoding; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Community; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.service.DSpaceObjectService; import org.dspace.core.Context; @@ -59,7 +60,7 @@ public class CommunityBuilder extends AbstractDSpaceObjectBuilder { } public CommunityBuilder withName(final String communityName) { - return setMetadataSingleValue(community, MetadataSchema.DC_SCHEMA, "title", null, communityName); + return setMetadataSingleValue(community, MetadataSchemaEnum.DC.getName(), "title", null, communityName); } public CommunityBuilder withLogo(String content) throws AuthorizeException, IOException, SQLException { @@ -83,7 +84,8 @@ public class CommunityBuilder extends AbstractDSpaceObjectBuilder { return community; } - protected void cleanup() throws Exception { + @Override + public void cleanup() throws Exception { delete(community); } @@ -91,4 +93,26 @@ public class CommunityBuilder extends AbstractDSpaceObjectBuilder { protected DSpaceObjectService getService() { return communityService; } + + /** + * Delete the Test Community referred to by the given UUID + * @param uuid UUID of Test Community to delete + * @throws SQLException + * @throws IOException + */ + public static void deleteCommunity(UUID uuid) throws SQLException, IOException { + try (Context c = new Context()) { + c.turnOffAuthorisationSystem(); + Community community = communityService.find(c, uuid); + if (community != null) { + try { + communityService.delete(c, community); + } catch (AuthorizeException e) { + // cannot occur, just wrap it to make the compiler happy + throw new RuntimeException(e.getMessage(), e); + } + } + c.complete(); + } + } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/EPersonBuilder.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/EPersonBuilder.java index 0c058f8610..32672f5427 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/EPersonBuilder.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/EPersonBuilder.java @@ -24,7 +24,8 @@ public class EPersonBuilder extends AbstractDSpaceObjectBuilder { super(context); } - protected void cleanup() throws Exception { + @Override + public void cleanup() throws Exception { delete(ePerson); } @@ -106,13 +107,4 @@ public class EPersonBuilder extends AbstractDSpaceObjectBuilder { ePersonService.setPassword(ePerson, password); return this; } - - @Override - /** - * Set a lower custom priority for the EPerson. It is one of the last object to delete to reduced the risk of - * pending references - */ - protected int getPriority() { - return 50; - } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/EntityTypeBuilder.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/EntityTypeBuilder.java new file mode 100644 index 0000000000..36b1e48f85 --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/EntityTypeBuilder.java @@ -0,0 +1,83 @@ +/** + * 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.builder; + +import java.sql.SQLException; + +import org.apache.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.EntityType; +import org.dspace.content.service.EntityTypeService; +import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; + +public class EntityTypeBuilder extends AbstractBuilder { + + /* Log4j logger*/ + private static final Logger log = Logger.getLogger(EntityTypeBuilder.class); + + private EntityType entityType; + + protected EntityTypeBuilder(Context context) { + super(context); + } + + @Override + protected EntityTypeService getService() { + return entityTypeService; + } + + @Override + public void cleanup() throws Exception { + delete(entityType); + } + + public EntityType build() { + try { + + entityTypeService.update(context, entityType); + context.dispatchEvents(); + + indexingService.commit(); + } catch (SearchServiceException | SQLException | AuthorizeException e) { + log.error(e); + } + return entityType; + } + + public void delete(EntityType entityType) throws Exception { + try (Context c = new Context()) { + c.turnOffAuthorisationSystem(); + EntityType attachedEntityType = c.reloadEntity(entityType); + if (attachedEntityType != null) { + getService().delete(c, attachedEntityType); + } + c.complete(); + } + + indexingService.commit(); + } + + public static EntityTypeBuilder createEntityTypeBuilder(Context context, String entityType) { + EntityTypeBuilder entityTypeBuilder = new EntityTypeBuilder(context); + return entityTypeBuilder.create(context, entityType); + } + + private EntityTypeBuilder create(Context context, String entityType) { + try { + + this.context = context; + this.entityType = entityTypeService.create(context, entityType); + + } catch (SQLException | AuthorizeException e) { + e.printStackTrace(); + } + + return this; + } +} \ No newline at end of file diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/GroupBuilder.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/GroupBuilder.java index 221aec5209..1004f9d94a 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/GroupBuilder.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/GroupBuilder.java @@ -32,7 +32,8 @@ public class GroupBuilder extends AbstractDSpaceObjectBuilder { } - protected void cleanup() throws Exception { + @Override + public void cleanup() throws Exception { delete(group); } @@ -104,14 +105,4 @@ public class GroupBuilder extends AbstractDSpaceObjectBuilder { } } - /** - * Set a lower custom priority for the Group. It could be referenced in several places. It needs to go first than - * EPersonBuilder - * - */ - @Override - protected int getPriority() { - return 80; - } - } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/ItemBuilder.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/ItemBuilder.java index 59738c238e..7cc32433b7 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/ItemBuilder.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/ItemBuilder.java @@ -10,7 +10,7 @@ package org.dspace.app.rest.builder; import org.dspace.content.Collection; import org.dspace.content.DCDate; import org.dspace.content.Item; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.WorkspaceItem; import org.dspace.content.service.DSpaceObjectService; import org.dspace.core.Context; @@ -52,19 +52,32 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder { } public ItemBuilder withTitle(final String title) { - return setMetadataSingleValue(item, MetadataSchema.DC_SCHEMA, "title", null, title); + return setMetadataSingleValue(item, MetadataSchemaEnum.DC.getName(), "title", null, title); } public ItemBuilder withIssueDate(final String issueDate) { - return addMetadataValue(item, MetadataSchema.DC_SCHEMA, "date", "issued", new DCDate(issueDate).toString()); + return addMetadataValue(item, MetadataSchemaEnum.DC.getName(), + "date", "issued", new DCDate(issueDate).toString()); } public ItemBuilder withAuthor(final String authorName) { - return addMetadataValue(item, MetadataSchema.DC_SCHEMA, "contributor", "author", authorName); + return addMetadataValue(item, MetadataSchemaEnum.DC.getName(), "contributor", "author", authorName); + } + + public ItemBuilder withPersonIdentifierFirstName(final String personIdentifierFirstName) { + return addMetadataValue(item, "person", "identifier", "firstname", personIdentifierFirstName); + } + + public ItemBuilder withPersonIdentifierLastName(final String personIdentifierLastName) { + return addMetadataValue(item, "person", "identifier", "lastname", personIdentifierLastName); } public ItemBuilder withSubject(final String subject) { - return addMetadataValue(item, MetadataSchema.DC_SCHEMA, "subject", null, subject); + return addMetadataValue(item, MetadataSchemaEnum.DC.getName(), "subject", null, subject); + } + + public ItemBuilder withRelationshipType(final String relationshipType) { + return addMetadataValue(item, "relationship", "type", null, relationshipType); } public ItemBuilder makeUnDiscoverable() { @@ -116,7 +129,8 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder { } } - protected void cleanup() throws Exception { + @Override + public void cleanup() throws Exception { delete(item); } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/MetadataFieldBuilder.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/MetadataFieldBuilder.java index a0a3d0e3df..1476cf0f24 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/MetadataFieldBuilder.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/MetadataFieldBuilder.java @@ -26,11 +26,6 @@ public class MetadataFieldBuilder extends AbstractBuilder @Override - protected void cleanup() throws Exception { + public void cleanup() throws Exception { if (workspaceItem != null) { deleteWsi(workspaceItem); } @@ -153,19 +153,19 @@ public class PoolTaskBuilder extends AbstractBuilder } public PoolTaskBuilder withTitle(final String title) { - return setMetadataSingleValue(MetadataSchema.DC_SCHEMA, "title", null, title); + return setMetadataSingleValue(MetadataSchemaEnum.DC.getName(), "title", null, title); } public PoolTaskBuilder withIssueDate(final String issueDate) { - return addMetadataValue(MetadataSchema.DC_SCHEMA, "date", "issued", new DCDate(issueDate).toString()); + return addMetadataValue(MetadataSchemaEnum.DC.getName(), "date", "issued", new DCDate(issueDate).toString()); } public PoolTaskBuilder withAuthor(final String authorName) { - return addMetadataValue(MetadataSchema.DC_SCHEMA, "contributor", "author", authorName); + return addMetadataValue(MetadataSchemaEnum.DC.getName(), "contributor", "author", authorName); } public PoolTaskBuilder withSubject(final String subject) { - return addMetadataValue(MetadataSchema.DC_SCHEMA, "subject", null, subject); + return addMetadataValue(MetadataSchemaEnum.DC.getName(), "subject", null, subject); } public PoolTaskBuilder grantLicense() { @@ -194,12 +194,4 @@ public class PoolTaskBuilder extends AbstractBuilder } return this; } - - @Override - /** - * Set a higher priority than workflowitem for the pooltask has it holds a reference to it - */ - protected int getPriority() { - return 300; - } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/RelationshipBuilder.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/RelationshipBuilder.java new file mode 100644 index 0000000000..f792f7e8ab --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/RelationshipBuilder.java @@ -0,0 +1,87 @@ +/** + * 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.builder; + +import java.sql.SQLException; + +import org.apache.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.Relationship; +import org.dspace.content.RelationshipType; +import org.dspace.content.service.RelationshipService; +import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; + +public class RelationshipBuilder extends AbstractBuilder { + + /* Log4j logger*/ + private static final Logger log = Logger.getLogger(RelationshipBuilder.class); + + private Relationship relationship; + + protected RelationshipBuilder(Context context) { + super(context); + } + + @Override + protected RelationshipService getService() { + return relationshipService; + } + + @Override + public void cleanup() throws Exception { + delete(relationship); + } + + public Relationship build() { + try { + + relationshipService.update(context, relationship); + context.dispatchEvents(); + + indexingService.commit(); + } catch (SearchServiceException | SQLException | AuthorizeException e) { + log.error(e); + } + return relationship; + } + + public void delete(Relationship dso) throws Exception { + try (Context c = new Context()) { + c.turnOffAuthorisationSystem(); + Relationship attachedDso = c.reloadEntity(dso); + if (attachedDso != null) { + getService().delete(c, attachedDso); + } + c.complete(); + } + + indexingService.commit(); + } + + public static RelationshipBuilder createRelationshipBuilder(Context context, Item leftItem, Item rightItem, + RelationshipType relationshipType) { + + RelationshipBuilder relationshipBuilder = new RelationshipBuilder(context); + return relationshipBuilder.create(context, leftItem, rightItem, relationshipType); + } + + private RelationshipBuilder create(Context context, Item leftItem, Item rightItem, + RelationshipType relationshipType) { + this.context = context; + + try { + relationship = relationshipService.create(context, leftItem, rightItem, relationshipType, 0, 0); + } catch (SQLException | AuthorizeException e) { + e.printStackTrace(); + } + + return this; + } +} diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/RelationshipTypeBuilder.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/RelationshipTypeBuilder.java new file mode 100644 index 0000000000..189f468322 --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/RelationshipTypeBuilder.java @@ -0,0 +1,107 @@ +/** + * 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.builder; + +import java.sql.SQLException; +import java.util.List; + +import org.apache.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.EntityType; +import org.dspace.content.Relationship; +import org.dspace.content.RelationshipType; +import org.dspace.content.service.RelationshipTypeService; +import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; + +public class RelationshipTypeBuilder extends AbstractBuilder { + + /* Log4j logger*/ + private static final Logger log = Logger.getLogger(RelationshipTypeBuilder.class); + + private RelationshipType relationshipType; + + protected RelationshipTypeBuilder(Context context) { + super(context); + } + + @Override + protected RelationshipTypeService getService() { + return relationshipTypeService; + } + + @Override + public void cleanup() throws Exception { + context.turnOffAuthorisationSystem(); + List byRelationshipType = relationshipService.findByRelationshipType(context, relationshipType); + for (Relationship relationship : byRelationshipType) { + relationshipService.delete(context, relationship); + } + context.restoreAuthSystemState(); + + delete(relationshipType); + } + + public RelationshipType build() { + try { + + relationshipTypeService.update(context, relationshipType); + context.dispatchEvents(); + + indexingService.commit(); + } catch (SearchServiceException | SQLException | AuthorizeException e) { + log.error(e); + } + return relationshipType; + } + + public void delete(RelationshipType relationshipType) throws Exception { + try (Context c = new Context()) { + c.turnOffAuthorisationSystem(); + RelationshipType attachedRelationShipType = c.reloadEntity(relationshipType); + if (attachedRelationShipType != null) { + getService().delete(c, attachedRelationShipType); + } + c.complete(); + } + + indexingService.commit(); + } + + public static RelationshipTypeBuilder createRelationshipTypeBuilder(Context context, EntityType leftType, + EntityType rightType, String leftLabel, + String rightLabel, Integer leftCardinalityMin, + Integer leftCardinalityMax, + Integer rightCardinalityMin, + Integer rightCardinalityMax) { + RelationshipTypeBuilder relationshipBuilder = new RelationshipTypeBuilder(context); + return relationshipBuilder.create(context, leftType, + rightType, leftLabel, + rightLabel, leftCardinalityMin, + leftCardinalityMax, rightCardinalityMin, + rightCardinalityMax); + } + + private RelationshipTypeBuilder create(Context context, EntityType leftEntityType, EntityType rightEntityType, + String leftLabel, String rightLabel, Integer leftCardinalityMin, + Integer leftCardinalityMax, Integer rightCardinalityMin, + Integer rightCardinalityMax) { + try { + + this.context = context; + this.relationshipType = relationshipTypeService + .create(context, leftEntityType, rightEntityType, leftLabel, rightLabel, leftCardinalityMin, + leftCardinalityMax, rightCardinalityMin, rightCardinalityMax); + + } catch (SQLException | AuthorizeException e) { + e.printStackTrace(); + } + + return this; + } +} diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/SiteBuilder.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/SiteBuilder.java index 3747675253..935a0fc56f 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/SiteBuilder.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/SiteBuilder.java @@ -20,7 +20,7 @@ public class SiteBuilder extends AbstractDSpaceObjectBuilder { } @Override - protected void cleanup() throws Exception { + public void cleanup() throws Exception { //Do nothing } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/WorkflowItemBuilder.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/WorkflowItemBuilder.java index facdcfa0b4..b0095c6373 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/WorkflowItemBuilder.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/builder/WorkflowItemBuilder.java @@ -17,7 +17,7 @@ import org.dspace.content.Collection; import org.dspace.content.DCDate; import org.dspace.content.Item; import org.dspace.content.LicenseUtils; -import org.dspace.content.MetadataSchema; +import org.dspace.content.MetadataSchemaEnum; import org.dspace.content.WorkspaceItem; import org.dspace.core.Context; import org.dspace.discovery.SearchServiceException; @@ -110,7 +110,7 @@ public class WorkflowItemBuilder extends AbstractBuilder> map = new LinkedHashMap<>(); + + /** + * Constructor that will initialize the Map with a predefined order for deletion + */ + public AbstractBuilderCleanupUtil() { + map.put(RelationshipBuilder.class.getName(), new LinkedList<>()); + map.put(RelationshipTypeBuilder.class.getName(), new LinkedList<>()); + map.put(EntityTypeBuilder.class.getName(), new LinkedList<>()); + map.put(PoolTaskBuilder.class.getName(), new LinkedList<>()); + map.put(WorkflowItemBuilder.class.getName(), new LinkedList<>()); + map.put(WorkspaceItemBuilder.class.getName(), new LinkedList<>()); + map.put(BitstreamBuilder.class.getName(), new LinkedList<>()); + map.put(BitstreamFormatBuilder.class.getName(), new LinkedList<>()); + map.put(ClaimedTaskBuilder.class.getName(), new LinkedList<>()); + map.put(CollectionBuilder.class.getName(), new LinkedList<>()); + map.put(CommunityBuilder.class.getName(), new LinkedList<>()); + map.put(EPersonBuilder.class.getName(), new LinkedList<>()); + map.put(GroupBuilder.class.getName(), new LinkedList<>()); + map.put(ItemBuilder.class.getName(), new LinkedList<>()); + map.put(MetadataFieldBuilder.class.getName(), new LinkedList<>()); + map.put(MetadataSchemaBuilder.class.getName(), new LinkedList<>()); + map.put(SiteBuilder.class.getName(), new LinkedList<>()); + + } + + /** + * Adds a builder to the map. + * This will make a new linkedList if the name doesn't exist yet as a key in the map with a list, if it already + * exists it will simply add the AbstractBuilder to that list. + * @param abstractBuilder The AbstractBuilder to be added + */ + public void addToMap(AbstractBuilder abstractBuilder) { + map.computeIfAbsent(abstractBuilder.getClass().getName(), k -> new LinkedList<>()).add(abstractBuilder); + } + + /** + * This method takes care of iterating over all the AbstractBuilders in the predefined order and calls + * the cleanup method to delete the objects from the database. + * @throws Exception If something goes wrong + */ + public void cleanupBuilders() throws Exception { + for (Map.Entry> entry : map.entrySet()) { + List list = entry.getValue(); + for (AbstractBuilder abstractBuilder : list) { + abstractBuilder.cleanup(); + } + } + } +} diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/csv/CsvImportIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/csv/CsvImportIT.java new file mode 100644 index 0000000000..0438595f31 --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/csv/CsvImportIT.java @@ -0,0 +1,236 @@ +/** + * 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.csv; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +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.io.BufferedWriter; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.dspace.app.rest.builder.CollectionBuilder; +import org.dspace.app.rest.builder.CommunityBuilder; +import org.dspace.app.rest.builder.ItemBuilder; +import org.dspace.app.rest.matcher.RelationshipMatcher; +import org.dspace.app.rest.test.AbstractEntityIntegrationTest; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.Relationship; +import org.dspace.content.service.EntityTypeService; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.RelationshipService; +import org.dspace.content.service.RelationshipTypeService; +import org.hamcrest.Matchers; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +public class CsvImportIT extends AbstractEntityIntegrationTest { + + @Autowired + private RelationshipTypeService relationshipTypeService; + + @Autowired + private EntityTypeService entityTypeService; + + @Autowired + private RelationshipService relationshipService; + + @Autowired + private ItemService itemService; + + @Test + public void createRelationshipsWithCsvImportTest() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + Collection col3 = CollectionBuilder.createCollection(context, child1).withName("OrgUnits").build(); + + Item article = ItemBuilder.createItem(context, col1) + .withTitle("Article") + .withIssueDate("2017-10-17") + .withRelationshipType("Publication") + .build(); + + Item itemB = validateSpecificItemRelationCreationCsvImport(col1, article, "TestItemB", "Person", + "isPublicationOfAuthor", + "Relationship list size is 1", 1, 0, 0); + Item itemC = validateSpecificItemRelationCreationCsvImport(col1, article, "TestItemC", "Person", + "isPublicationOfAuthor", + "Relationship list size is 1", 1, 1, 0); + Item itemD = validateSpecificItemRelationCreationCsvImport(col1, article, "TestItemD", "Project", + "isPublicationOfProject", + "Relationship list size is 1", 1, 0, 0); + Item itemE = validateSpecificItemRelationCreationCsvImportMultiple(col1, "TestItemE", "Publication", + "isAuthorOfPublication", + "Relationship list size is 2", 2, 0, 1, + itemC, itemB); + + List relationships = relationshipService.findByItem(context, itemE); + getClient().perform(get("/api/core/relationships/" + relationships.get(0).getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.leftPlace", is(0))) + .andExpect(jsonPath("$.rightId", is(itemC.getID().toString()))) + .andExpect(jsonPath("$.rightPlace", is(1))) + .andExpect(jsonPath("$", Matchers.is(RelationshipMatcher.matchRelationship(relationships.get(0))))); + getClient().perform(get("/api/core/relationships/" + relationships.get(1).getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.leftPlace", is(1))) + .andExpect(jsonPath("$.rightId", is(itemB.getID().toString()))) + .andExpect(jsonPath("$.rightPlace", is(1))) + .andExpect(jsonPath("$", Matchers.is(RelationshipMatcher.matchRelationship(relationships.get(1))))); + + Item itemF = validateSpecificItemRelationCreationCsvImport(col1, itemE, "TestItemF", "Person", + "isPublicationOfAuthor", + "Relationship list size is 1", 1, 2, 0); + + UpdateItemEToDeleteRelationshipToC(itemE, itemB, itemF, col1, "TestItemE"); + + getClient().perform(get("/api/core/items/" + itemE.getID())).andExpect(status().isOk()); + + assertItemERelationships(itemB, itemE, itemF); + + updateArticleItemToAddAnotherRelationship(col1, article, itemB, itemC, itemF); + + getClient().perform(get("/api/core/items/" + article.getID())).andExpect(status().isOk()); + + assertArticleRelationships(article, itemB, itemC, itemF); + + } + + private void assertItemERelationships(Item itemB, Item itemE, Item itemF) throws SQLException { + List relationshipsForItemE = relationshipService.findByItem(context, itemE); + assertThat(relationshipsForItemE.size(), is(2)); + assertThat(relationshipsForItemE.get(0).getRightItem(), is(itemF)); + assertThat(relationshipsForItemE.get(1).getRightItem(), is(itemB)); + } + + private void assertArticleRelationships(Item article, Item itemB, Item itemC, Item itemF) throws SQLException { + List relationshipsForArticle = relationshipService + .findByItemAndRelationshipType(context, article, relationshipTypeService + .findbyTypesAndLabels(context, entityTypeService.findByEntityType(context, "Publication"), + entityTypeService.findByEntityType(context, "Person"), "isAuthorOfPublication", + "isPublicationOfAuthor")); + assertThat(relationshipsForArticle.size(), is(3)); + List expectedRelationshipsItemsForArticle = new ArrayList<>(); + expectedRelationshipsItemsForArticle.add(itemC); + expectedRelationshipsItemsForArticle.add(itemF); + expectedRelationshipsItemsForArticle.add(itemB); + + List actualRelationshipsItemsForArticle = new ArrayList<>(); + for (Relationship relationship : relationshipsForArticle) { + if (relationship.getLeftItem().getID() == article.getID()) { + actualRelationshipsItemsForArticle.add(relationship.getLeftItem()); + } else { + actualRelationshipsItemsForArticle.add(relationship.getRightItem()); + } + } + assertThat(true, Matchers.is(actualRelationshipsItemsForArticle + .containsAll(expectedRelationshipsItemsForArticle))); + } + + private void updateArticleItemToAddAnotherRelationship(Collection col1, Item article, Item itemB, Item itemC, + Item itemF) throws Exception { + String csvLineString = article.getID().toString() + "," + col1 + .getHandle() + "," + "Article" + "," + "Publication" + "," + + itemB.getID().toString() + "||" + itemC.getID().toString() + "||" + itemF + .getID().toString(); + String[] csv = {"id,collection,dc.title,relationship.type,relation." + "isAuthorOfPublication", csvLineString}; + performImportScript(csv); + } + + private void UpdateItemEToDeleteRelationshipToC(Item itemE, Item itemB, Item itemF, Collection owningCollection, + String title) throws Exception { + String csvLineString = itemE.getID().toString() + "," + owningCollection + .getHandle() + "," + title + "," + "Person" + "," + itemB.getID().toString() + "||" + itemF.getID() + .toString(); + String[] csv = {"id,collection,dc.title,relationship.type,relation." + "isAuthorOfPublication", csvLineString}; + performImportScript(csv); + + } + + private Item validateSpecificItemRelationCreationCsvImport(Collection col1, Item relatedItem, String itemTitle, + String relationshipType, + String relationshipTypeLabel, + String reasonAssertCheck, Integer sizeToCheck, + Integer leftPlaceToCheck, + Integer rightPlaceToCheck) throws Exception { + String csvLineString = "+," + col1.getHandle() + "," + itemTitle + "," + relationshipType + "," + relatedItem + .getID().toString(); + String[] csv = {"id,collection,dc.title,relationship.type,relation." + relationshipTypeLabel, csvLineString}; + performImportScript(csv); + Iterator itemIteratorItem = itemService.findByMetadataField(context, "dc", "title", null, itemTitle); + Item item = itemIteratorItem.next(); + + List relationships = relationshipService.findByItem(context, item); + assertThat(reasonAssertCheck, relationships.size(), equalTo(sizeToCheck)); + getClient().perform(get("/api/core/items/" + item.getID())).andExpect(status().isOk()); + getClient().perform(get("/api/core/relationships/" + relationships.get(0).getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.leftPlace", is(leftPlaceToCheck))) + .andExpect(jsonPath("$.rightPlace", is(rightPlaceToCheck))) + .andExpect(jsonPath("$", Matchers.is(RelationshipMatcher.matchRelationship(relationships.get(0))))); + + return item; + } + + private Item validateSpecificItemRelationCreationCsvImportMultiple(Collection col1, String itemTitle, + String relationshipType, + String relationshipTypeLabel, + String reasonAssertCheck, Integer sizeToCheck, + Integer leftPlaceToCheck, + Integer rightPlaceToCheck, Item... relatedItem) + throws Exception { + String idStringRelatedItems = ""; + for (Item item : relatedItem) { + idStringRelatedItems += item.getID().toString(); + idStringRelatedItems += "||"; + } + idStringRelatedItems = idStringRelatedItems.substring(0, idStringRelatedItems.length() - 2); + String csvLineString = "+," + col1 + .getHandle() + "," + itemTitle + "," + relationshipType + "," + idStringRelatedItems; + String[] csv = {"id,collection,dc.title,relationship.type,relation." + relationshipTypeLabel, csvLineString}; + performImportScript(csv); + Iterator itemIteratorItem = itemService.findByMetadataField(context, "dc", "title", null, itemTitle); + Item item = itemIteratorItem.next(); + + + return item; + } + + private void performImportScript(String[] csv) throws Exception { + String filename = "test.csv"; + BufferedWriter out = new BufferedWriter( + new OutputStreamWriter( + new FileOutputStream(filename), "UTF-8")); + for (String csvLine : csv) { + out.write(csvLine + "\n"); + } + out.flush(); + out.close(); + out = null; + + runDSpaceScript("metadata-import", "-f", "test.csv", "-e", "admin@email.com", "-s"); + } +} diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/EntityTypeMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/EntityTypeMatcher.java new file mode 100644 index 0000000000..6e86c2d3fe --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/EntityTypeMatcher.java @@ -0,0 +1,59 @@ +/** + * 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.containsString; +import static org.hamcrest.Matchers.is; + +import org.dspace.content.EntityType; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; + +public class EntityTypeMatcher { + + private EntityTypeMatcher() {} + + public static Matcher matchEntityTypeEntry(EntityType entityType) { + return matchEntityTypeExplicitValuesEntry(entityType.getID(), entityType.getLabel()); + } + + public static Matcher matchEntityTypeEntryForLabel(String label) { + return matchEntityTypeExplicitValuesEntry(0, label); + } + + private static Matcher matchId(int id) { + return id == 0 ? + allOf( + hasJsonPath("$.id", Matchers.not(Matchers.empty())) + ) : + allOf( + hasJsonPath("$.id", Matchers.is(id)) + ); + } + + private static Matcher matchSelfLink(int id) { + return id == 0 ? + allOf( + hasJsonPath("$._links.self.href", containsString("/api/core/entitytypes/")) + ) : + allOf( + hasJsonPath("$._links.self.href", containsString("/api/core/entitytypes/" + id)) + ); + } + + public static Matcher matchEntityTypeExplicitValuesEntry(int id, String label) { + return allOf( + matchId(id), + hasJsonPath("$.label", is(label)), + hasJsonPath("$.type", is("entitytype")), + matchSelfLink(id) + ); + } +} diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java index 5cca3a7f42..5e3c477506 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java @@ -139,5 +139,12 @@ public class FacetEntryMatcher { return anyOf(hasJsonPath("$.next.href", containsString(path)), not(hasJsonPath("$.next.href", containsString(path)))); } - + public static Matcher entityTypeFacet(boolean hasNext) { + return allOf( + hasJsonPath("$.name", is("entityType")), + hasJsonPath("$.facetLimit", any(Integer.class)), + hasJsonPath("$._links.self.href", containsString("api/discover/facets/entityType")), + hasJsonPath("$._links", matchNextLink(hasNext, "api/discover/facets/entityType")) + ); + } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/RelationshipMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/RelationshipMatcher.java new file mode 100644 index 0000000000..20a855ff5e --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/RelationshipMatcher.java @@ -0,0 +1,50 @@ +/** + * 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 java.util.UUID; + +import org.dspace.content.Item; +import org.dspace.content.Relationship; +import org.dspace.content.RelationshipType; +import org.hamcrest.Matcher; + +public class RelationshipMatcher { + + private RelationshipMatcher() {} + + public static Matcher matchRelationship(Relationship relationship) { + return matchRelationshipExplicitValues(relationship.getLeftItem(), relationship.getRightItem(), + relationship.getLeftPlace(), relationship.getRightPlace(), + relationship.getRelationshipType()); + } + + private static Matcher matchRelationshipExplicitValues(Item leftItem, Item rightItem, int leftPlace, + int rightPlace, + RelationshipType relationshipType) { + return matchRelationshipExplicitObjectValues(leftItem.getID(), rightItem.getID(), leftPlace, rightPlace, + relationshipType); + } + + private static Matcher matchRelationshipExplicitObjectValues(UUID leftId, UUID rightId, + int leftPlace, int rightPlace, + RelationshipType relationshipType) { + return allOf( + hasJsonPath("$.leftId", is(leftId.toString())), + hasJsonPath("$.rightId", is(rightId.toString())), + hasJsonPath("$.leftPlace", is(leftPlace)), + hasJsonPath("$.rightPlace", is(rightPlace)), + hasJsonPath("$._embedded.relationshipType", + RelationshipTypeMatcher.matchRelationshipTypeEntry(relationshipType)) + ); + } +} diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/RelationshipTypeMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/RelationshipTypeMatcher.java new file mode 100644 index 0000000000..55fd089113 --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/RelationshipTypeMatcher.java @@ -0,0 +1,87 @@ +/** + * 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.containsString; +import static org.hamcrest.Matchers.is; + +import org.dspace.content.EntityType; +import org.dspace.content.RelationshipType; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; + +public class RelationshipTypeMatcher { + + private RelationshipTypeMatcher() {} + + public static Matcher matchRelationshipTypeEntry(RelationshipType relationshipType) { + return matchRelationshipTypeExplicitEntityTypes(relationshipType, relationshipType.getLeftType(), + relationshipType.getRightType()); + } + + private static Matcher matchRelationshipTypeExplicitEntityTypes(RelationshipType relationshipType, + EntityType leftType, + EntityType rightType) { + return matchRelationshipTypeExplicitEntityTypeValues(relationshipType, leftType.getID(), leftType.getLabel(), + rightType.getID(), rightType.getLabel()); + } + + private static Matcher matchRelationshipTypeExplicitEntityTypeValues( + RelationshipType relationshipType, int leftEntityTypeId, String leftEntityTypeLabel, int rightEntityTypeId, + String rightEntityTypeLabel) { + + return matchExplicitRelationshipTypeValuesAndExplicitEntityTypeValues(relationshipType.getID(), + relationshipType.getLeftLabel(), + relationshipType.getRightLabel(), + relationshipType.getLeftMinCardinality(), + relationshipType.getLeftMaxCardinality(), + relationshipType.getRightMinCardinality(), + relationshipType.getRightMaxCardinality(), + leftEntityTypeId, leftEntityTypeLabel, + rightEntityTypeId, rightEntityTypeLabel); + } + + private static Matcher matchExplicitRelationshipTypeValuesAndExplicitEntityType(int id, + String leftLabel, String rightLabel, Integer leftMinCardinality, Integer leftMaxCardinality, + Integer rightMinCardinality, Integer rightMaxCardinality, + EntityType leftEntityType, EntityType rightEntityType) { + return matchExplicitRelationshipTypeValuesAndExplicitEntityTypeValues(id, leftLabel, rightLabel, + leftMinCardinality, leftMaxCardinality, + rightMinCardinality, + rightMaxCardinality, + leftEntityType.getID(), + leftEntityType.getLabel(), + rightEntityType.getID(), + rightEntityType.getLabel()); + } + + private static Matcher matchExplicitRelationshipTypeValuesAndExplicitEntityTypeValues(int id, + String leftLabel, String rightLabel, Integer leftMinCardinality, Integer leftMaxCardinality, + Integer rightMinCardinality, Integer rightMaxCardinality, int leftEntityTypeId, String leftEntityTypeLabel, + int rightEntityTypeId, String rightEntityTypeLabel) { + return allOf( + hasJsonPath("$.id", is(id)), + hasJsonPath("$.leftLabel", is(leftLabel)), + hasJsonPath("$.rightLabel", is(rightLabel)), + hasJsonPath("$.leftMinCardinality", is(leftMinCardinality)), + hasJsonPath("$.leftMaxCardinality", is(leftMaxCardinality)), + hasJsonPath("$.rightMinCardinality", is(rightMinCardinality)), + hasJsonPath("$.rightMaxCardinality", is(rightMaxCardinality)), + hasJsonPath("$.type", is("relationshiptype")), + hasJsonPath("$._links.self.href", containsString("/api/core/relationshiptypes/" + id)), + hasJsonPath("$._embedded.leftType", Matchers.allOf( + EntityTypeMatcher.matchEntityTypeExplicitValuesEntry(leftEntityTypeId, leftEntityTypeLabel) + )), + hasJsonPath("$._embedded.rightType", Matchers.is( + EntityTypeMatcher.matchEntityTypeExplicitValuesEntry(rightEntityTypeId, rightEntityTypeLabel) + )) + ); + } +} \ No newline at end of file diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/SearchFilterMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/SearchFilterMatcher.java index da344e47b4..8238d378df 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/SearchFilterMatcher.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/SearchFilterMatcher.java @@ -102,4 +102,58 @@ public class SearchFilterMatcher { )) ); } + public static Matcher entityTypeFilter() { + return allOf( + hasJsonPath("$.filter", is("entityType")), + hasJsonPath("$.hasFacets", is(true)), + hasJsonPath("$.type", is("text")), + hasJsonPath("$.openByDefault", is(false)), + checkOperators() + ); + } + public static Matcher isAuthorOfPublicationRelation() { + return allOf( + hasJsonPath("$.filter", is("isAuthorOfPublication")), + hasJsonPath("$.hasFacets", is(false)), + hasJsonPath("$.type", is("text")), + hasJsonPath("$.openByDefault", is(false)), + checkOperators() + ); + } + public static Matcher isProjectOfPublicationRelation() { + return allOf( + hasJsonPath("$.filter", is("isProjectOfPublication")), + hasJsonPath("$.hasFacets", is(false)), + hasJsonPath("$.type", is("text")), + hasJsonPath("$.openByDefault", is(false)), + checkOperators() + ); + } + public static Matcher isOrgUnitOfPublicationRelation() { + return allOf( + hasJsonPath("$.filter", is("isOrgUnitOfPublication")), + hasJsonPath("$.hasFacets", is(false)), + hasJsonPath("$.type", is("text")), + hasJsonPath("$.openByDefault", is(false)), + checkOperators() + ); + } + public static Matcher isPublicationOfJournalIssueRelation() { + return allOf( + hasJsonPath("$.filter", is("isPublicationOfJournalIssue")), + hasJsonPath("$.hasFacets", is(false)), + hasJsonPath("$.type", is("text")), + hasJsonPath("$.openByDefault", is(false)), + checkOperators() + ); + } + public static Matcher isJournalOfPublicationRelation() { + return allOf( + hasJsonPath("$.filter", is("isJournalOfPublication")), + hasJsonPath("$.hasFacets", is(false)), + hasJsonPath("$.type", is("text")), + hasJsonPath("$.openByDefault", is(false)), + checkOperators() + ); + } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java index 429161ea37..507ea02c0c 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java @@ -22,6 +22,7 @@ import org.apache.commons.io.Charsets; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.Application; import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.utils.DSpaceConfigurationInitializer; import org.dspace.app.rest.utils.DSpaceKernelInitializer; import org.junit.Assert; import org.junit.runner.RunWith; @@ -44,11 +45,18 @@ import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; import org.springframework.web.context.WebApplicationContext; /** - * Abstract controller integration test class that will take care of setting up the - * Spring Boot environment to run the integration test + * Abstract integration test class that will take care of setting up the Spring Boot environment to run + * integration tests against @Controller classes (Spring Controllers). + *

+ * This Abstract class uses Spring Boot's default mock environment testing scheme, which relies on MockMvc to "mock" + * a webserver and call Spring Controllers directly. This avoids the cost of starting a webserver. + *

+ * If you need to test a Servlet (or something not a Spring Controller), you will NOT be able to use this class. + * Instead, please use the AbstractWebClientIntegrationTest in this same package. * * @author Tom Desair * @author Tim Donohue + * @see org.dspace.app.rest.test.AbstractWebClientIntegrationTest */ // Run tests with JUnit 4 and Spring TestContext Framework @RunWith(SpringRunner.class) @@ -56,8 +64,8 @@ import org.springframework.web.context.WebApplicationContext; // NOTE: By default, Spring caches and reuses ApplicationContext for each integration test (to speed up tests) // See: https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#integration-testing @SpringBootTest(classes = Application.class) -// Load DSpaceKernelInitializer in Spring ApplicationContext (to initialize DSpace Kernel) -@ContextConfiguration(initializers = DSpaceKernelInitializer.class) +// Load DSpace initializers in Spring ApplicationContext (to initialize DSpace Kernel & Configuration) +@ContextConfiguration(initializers = { DSpaceKernelInitializer.class, DSpaceConfigurationInitializer.class }) // Tell Spring to make ApplicationContext an instance of WebApplicationContext (for web-based tests) @WebAppConfiguration public class AbstractControllerIntegrationTest extends AbstractIntegrationTestWithDatabase { @@ -112,7 +120,7 @@ public class AbstractControllerIntegrationTest extends AbstractIntegrationTestWi if (StringUtils.isNotBlank(authToken)) { mockMvcBuilder.defaultRequest( - get("").header(AUTHORIZATION_HEADER, AUTHORIZATION_TYPE + authToken)); + get("").header(AUTHORIZATION_HEADER, AUTHORIZATION_TYPE + authToken)); } return mockMvcBuilder @@ -128,8 +136,8 @@ public class AbstractControllerIntegrationTest extends AbstractIntegrationTestWi public String getAuthToken(String user, String password) throws Exception { return StringUtils.substringAfter( - getAuthResponse(user, password).getHeader(AUTHORIZATION_HEADER), - AUTHORIZATION_TYPE); + getAuthResponse(user, password).getHeader(AUTHORIZATION_HEADER), + AUTHORIZATION_TYPE); } public String getPatchContent(List ops) { @@ -149,4 +157,3 @@ public class AbstractControllerIntegrationTest extends AbstractIntegrationTestWi }; } } - diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractEntityIntegrationTest.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractEntityIntegrationTest.java new file mode 100644 index 0000000000..5308551aaf --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractEntityIntegrationTest.java @@ -0,0 +1,105 @@ +/** + * 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.test; + +import org.dspace.app.rest.builder.EntityTypeBuilder; +import org.dspace.app.rest.builder.RelationshipTypeBuilder; +import org.dspace.content.EntityType; +import org.dspace.content.service.EntityTypeService; +import org.junit.Before; +import org.springframework.beans.factory.annotation.Autowired; + +public class AbstractEntityIntegrationTest extends AbstractControllerIntegrationTest { + + @Autowired + private EntityTypeService entityTypeService; + + /** + * This method will call the setUp method from AbstractControllerIntegrationTest. + * Afterwards it will setUp the entity relation structure as defined in + * dspace-api/src/test/data/dspaceFolder/config/entities/relationship-types.xml + * + * This method will first build the following EntityTypes: + * - Publication + * - Person + * - Project + * - OrgUnit + * - Journal + * - JournalVolume + * - JournalIssue + * + * After the EntityTypes are created, RelationshipTypes are set up between the different EntityTypes as indicated + * in relationship-types.xml + */ + @Before + public void setUp() throws Exception { + super.setUp(); + + if (entityTypeService.findAll(context).size() > 0) { + //Don't initialize the setup more than once + return; + } + + context.turnOffAuthorisationSystem(); + + EntityType publication = EntityTypeBuilder.createEntityTypeBuilder(context, "Publication").build(); + EntityType person = EntityTypeBuilder.createEntityTypeBuilder(context, "Person").build(); + EntityType project = EntityTypeBuilder.createEntityTypeBuilder(context, "Project").build(); + EntityType orgUnit = EntityTypeBuilder.createEntityTypeBuilder(context, "OrgUnit").build(); + EntityType journal = EntityTypeBuilder.createEntityTypeBuilder(context, "Journal").build(); + EntityType journalVolume = EntityTypeBuilder.createEntityTypeBuilder(context, "JournalVolume").build(); + EntityType journalIssue = EntityTypeBuilder.createEntityTypeBuilder(context, "JournalIssue").build(); + + + RelationshipTypeBuilder.createRelationshipTypeBuilder(context, publication, person, "isAuthorOfPublication", + "isPublicationOfAuthor", 0, null, 0, + null).build(); + + RelationshipTypeBuilder.createRelationshipTypeBuilder(context, publication, project, "isProjectOfPublication", + "isPublicationOfProject", 0, null, 0, + null).build(); + + RelationshipTypeBuilder.createRelationshipTypeBuilder(context, publication, orgUnit, "isOrgUnitOfPublication", + "isPublicationOfOrgUnit", 0, null, 0, + null).build(); + + RelationshipTypeBuilder.createRelationshipTypeBuilder(context, person, project, "isProjectOfPerson", + "isPersonOfProject", 0, null, 0, + null).build(); + + RelationshipTypeBuilder.createRelationshipTypeBuilder(context, person, orgUnit, "isOrgUnitOfPerson", + "isPersonOfOrgUnit", 0, null, 0, + null).build(); + + RelationshipTypeBuilder.createRelationshipTypeBuilder(context, project, orgUnit, "isOrgUnitOfProject", + "isProjectOfOrgUnit", 0, null, 0, + null).build(); + + RelationshipTypeBuilder.createRelationshipTypeBuilder(context, journal, journalVolume, "isVolumeOfJournal", + "isJournalOfVolume", 0, null, 1, + null).build(); + + RelationshipTypeBuilder.createRelationshipTypeBuilder(context, journalVolume, journalIssue, + "isIssueOfJournalVolume", "isJournalVolumeOfIssue", 0, + null, 1, + 1).build(); + + RelationshipTypeBuilder.createRelationshipTypeBuilder(context, publication, orgUnit, "isAuthorOfPublication", + "isPublicationOfAuthor", 0, null, 0, + null).build(); + + RelationshipTypeBuilder.createRelationshipTypeBuilder(context, journalIssue, publication, + "isPublicationOfJournalIssue", + "isJournalIssueOfPublication", 0, null, 0, + 1).build(); + + context.restoreAuthSystemState(); + } + + +} diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractIntegrationTestWithDatabase.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractIntegrationTestWithDatabase.java index 2aae82f96c..b655cb8e23 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractIntegrationTestWithDatabase.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractIntegrationTestWithDatabase.java @@ -184,6 +184,9 @@ public class AbstractIntegrationTestWithDatabase extends AbstractDSpaceIntegrati .getServiceByName(SearchService.class.getName(), MockSolrServiceImpl.class); searchService.reset(); + // Reload our ConfigurationService (to reset configs to defaults again) + DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig(); + // NOTE: we explicitly do NOT destroy our default eperson & admin as they // are cached and reused for all tests. This speeds up all tests. } catch (Exception e) { diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java new file mode 100644 index 0000000000..26fa7f0ef7 --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractWebClientIntegrationTest.java @@ -0,0 +1,118 @@ +/** + * 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.test; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.Application; +import org.dspace.app.rest.utils.DSpaceConfigurationInitializer; +import org.dspace.app.rest.utils.DSpaceKernelInitializer; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.context.ApplicationContext; +import org.springframework.http.HttpEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Abstract web client integration test class that will initialize the Spring Boot test environment by starting up + * a full test webserver (on a random port). + *

+ * As running a test webserver is an expensive operation, this Abstract class is only necessary to perform + * Integration Tests on *Servlets*. If you are performing integration tests on a Spring Controller + * (@Controller annotation), you should use AbstractControllerIntegrationTest + *

+ * NOTE: The annotations on this class should be kept in sync with those on AbstractControllerIntegrationTest. + * The ONLY differences should be in the "webEnvironment" param passed to @SpringBootTest, and the removal + * of @WebAppConfiguration (which is only allowed in a mock environment) + * + * @author Tim Donohue + * @see org.dspace.app.rest.test.AbstractControllerIntegrationTest + */ +// Run tests with JUnit 4 and Spring TestContext Framework +@RunWith(SpringRunner.class) +// Specify main class to use to load Spring ApplicationContext +// ALSO tell Spring to start a web server on a random port +// NOTE: By default, Spring caches and reuses ApplicationContext for each integration test (to speed up tests) +// See: https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#integration-testing +@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +// Load DSpace initializers in Spring ApplicationContext (to initialize DSpace Kernel & Configuration) +@ContextConfiguration(initializers = { DSpaceKernelInitializer.class, DSpaceConfigurationInitializer.class }) +public class AbstractWebClientIntegrationTest extends AbstractIntegrationTestWithDatabase { + // (Random) port chosen for test web server + @LocalServerPort + private int port; + + // RestTemplate class with access to test web server + @Autowired + private TestRestTemplate restTemplate; + + // Spring Application context + @Autowired + protected ApplicationContext applicationContext; + + /** + * Get client TestRestTemplate for making HTTP requests to test webserver + * @return TestRestTemplate + */ + public TestRestTemplate getClient() { + return restTemplate; + } + + /** + * Return the full URL of a request at a specific path. + * (http://localhost:[port][path]) + * @param path Path (should start with a slash) + * @return full URL + */ + public String getURL(String path) { + return "http://localhost:" + port + path; + } + + /** + * Perform a GET request and return response as a String + * @param path path to perform GET against + * @return ResponseEntity with a String body + */ + public ResponseEntity getResponseAsString(String path) { + return getClient().getForEntity(getURL(path), String.class); + } + + /** + * Perform an authenticated (via Basic Auth) GET request and return response as a String + * @param path path to perform GET against + * @param username Username + * @param password Password + * @return ResponseEntity with a String body + */ + public ResponseEntity getResponseAsString(String path, String username, String password) { + return getClient().withBasicAuth(username, password).getForEntity(getURL(path), String.class); + } + + /** + * Perform an authenticated (via Basic Auth) POST request and return response as a String. + * @param path path to perform GET against + * @param username Username (may be null to perform an unauthenticated POST) + * @param password Password + * @return ResponseEntity with a String body + */ + public ResponseEntity postResponseAsString(String path, String username, String password, + HttpEntity requestEntity) { + // If username is not empty, perform an authenticated POST. Else attempt without AuthN + if (StringUtils.isNotBlank(username)) { + return getClient().withBasicAuth(username, password).postForEntity(getURL(path), requestEntity, + String.class); + } else { + return getClient().postForEntity(getURL(path), requestEntity, String.class); + } + } +} + diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/sword/Swordv1IT.java b/dspace-spring-rest/src/test/java/org/dspace/app/sword/Swordv1IT.java new file mode 100644 index 0000000000..24244e1773 --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/sword/Swordv1IT.java @@ -0,0 +1,117 @@ +/** + * 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.sword; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; + +import org.dspace.app.rest.test.AbstractWebClientIntegrationTest; +import org.dspace.services.ConfigurationService; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.TestPropertySource; + +/** + * Integration test to verify that the /sword endpoint is responding as a valid SWORD endpoint. + * This tests that our dspace-sword module is running at this endpoint. + *

+ * This is a AbstractWebClientIntegrationTest because testing dspace-sword requires + * running a web server (as dspace-sword makes use of Servlets, not Controllers). + * + * @author Tim Donohue + */ +// Ensure the SWORD SERVER IS ENABLED before any tests run. +// This annotation overrides default DSpace config settings loaded into Spring Context +@TestPropertySource(properties = {"sword-server.enabled = true"}) +public class Swordv1IT extends AbstractWebClientIntegrationTest { + + @Autowired + private ConfigurationService configurationService; + + // All SWORD paths that we test against + private final String SERVICE_DOC_PATH = "/sword/servicedocument"; + private final String DEPOSIT_PATH = "/sword/deposit"; + private final String MEDIA_LINK_PATH = "/sword/media-link"; + + @Before + public void onlyRunIfConfigExists() { + // These integration tests REQUIRE that SWORDWebConfig is found/available (as this class deploys SWORD) + // If this class is not available, the below "Assume" will cause all tests to be SKIPPED + // NOTE: SWORDWebConfig is provided by the 'dspace-sword' module + try { + Class.forName("org.dspace.app.configuration.SWORDWebConfig"); + } catch (ClassNotFoundException ce) { + Assume.assumeNoException(ce); + } + + // Ensure SWORD URL configurations are set correctly (based on our integration test server's paths) + // SWORD validates requests against these configs, and throws a 404 if they don't match the request path + configurationService.setProperty("sword-server.servicedocument.url", getURL(SERVICE_DOC_PATH)); + configurationService.setProperty("sword-server.deposit.url", getURL(DEPOSIT_PATH)); + configurationService.setProperty("sword-server.media-link.url", getURL(MEDIA_LINK_PATH)); + } + + @Test + public void serviceDocumentUnauthorizedTest() throws Exception { + // Attempt to load the ServiceDocument without first authenticating + ResponseEntity response = getResponseAsString(SERVICE_DOC_PATH); + // Expect a 401 response code + assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + } + + @Test + public void serviceDocumentTest() throws Exception { + // Attempt to load the ServiceDocument as an Admin user. + ResponseEntity response = getResponseAsString(SERVICE_DOC_PATH, + admin.getEmail(), password); + // Expect a 200 response code, and an ATOM UTF-8 document + assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); + assertThat(response.getHeaders().getContentType().toString(), equalTo("application/atomsvc+xml;charset=UTF-8")); + + // Check for SWORD version in response body + assertThat(response.getBody(), containsString("1.3")); + } + + @Test + public void depositUnauthorizedTest() throws Exception { + // Attempt to access /deposit endpoint without sending authentication information + ResponseEntity response = postResponseAsString(DEPOSIT_PATH, null, null, null); + // Expect a 401 response code + assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + } + + @Test + @Ignore + public void depositTest() throws Exception { + // TODO: Actually test a full deposit via SWORD. + // Currently, we are just ensuring the /deposit endpoint exists (see above) and isn't throwing a 404 + } + + @Test + public void mediaLinkUnauthorizedTest() throws Exception { + // Attempt to access /media-link endpoint without sending authentication information + ResponseEntity response = getResponseAsString(MEDIA_LINK_PATH); + // Expect a 401 response code + assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + } + + @Test + @Ignore + public void mediaLinkTest() throws Exception { + // TODO: Actually test a /media-link request. + // Currently, we are just ensuring the /media-link endpoint exists (see above) and isn't throwing a 404 + } +} + diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/sword2/Swordv2IT.java b/dspace-spring-rest/src/test/java/org/dspace/app/sword2/Swordv2IT.java new file mode 100644 index 0000000000..95ec762514 --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/sword2/Swordv2IT.java @@ -0,0 +1,149 @@ +/** + * 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.sword2; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; + +import org.dspace.app.rest.test.AbstractWebClientIntegrationTest; +import org.dspace.services.ConfigurationService; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.TestPropertySource; + +/** + * Integration test to verify the /swordv2 endpoint is responding as a valid SWORDv2 endpoint. + * This tests that our dspace-swordv2 module is running at this endpoint. + *

+ * This is a AbstractWebClientIntegrationTest because testing dspace-swordv2 requires + * running a web server (as dspace-swordv2 makes use of Servlets, not Controllers). + * + * @author Tim Donohue + */ +// Ensure the SWORDv2 SERVER IS ENABLED before any tests run. +// This annotation overrides default DSpace config settings loaded into Spring Context +@TestPropertySource(properties = {"swordv2-server.enabled = true"}) +public class Swordv2IT extends AbstractWebClientIntegrationTest { + + @Autowired + private ConfigurationService configurationService; + + // All SWORD v2 paths that we test against + private final String SERVICE_DOC_PATH = "/swordv2/servicedocument"; + private final String COLLECTION_PATH = "/swordv2/collection"; + private final String MEDIA_RESOURCE_PATH = "/swordv2/edit-media"; + private final String CONTAINER_PATH = "/swordv2/edit"; + private final String STATEMENT_PATH = "/swordv2/statement"; + + @Before + public void onlyRunIfConfigExists() { + // These integration tests REQUIRE that SWORDv2WebConfig is found/available (as this class deploys SWORDv2) + // If this class is not available, the below "Assume" will cause all tests to be SKIPPED + // NOTE: SWORDv2WebConfig is provided by the 'dspace-swordv2' module + try { + Class.forName("org.dspace.app.configuration.SWORDv2WebConfig"); + } catch (ClassNotFoundException ce) { + Assume.assumeNoException(ce); + } + + // Ensure SWORDv2 URL configurations are set correctly (based on our integration test server's paths) + // SWORDv2 validates requests against these configs, and throws a 404 if they don't match the request path + configurationService.setProperty("swordv2-server.servicedocument.url", getURL(SERVICE_DOC_PATH)); + } + + @Test + public void serviceDocumentUnauthorizedTest() throws Exception { + // Attempt to GET the ServiceDocument without first authenticating + ResponseEntity response = getResponseAsString(SERVICE_DOC_PATH); + // Expect a 401 response code + assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + } + + @Test + public void serviceDocumentTest() throws Exception { + // Attempt to GET the ServiceDocument as an Admin user. + ResponseEntity response = getResponseAsString(SERVICE_DOC_PATH, + admin.getEmail(), password); + // Expect a 200 response code, and an ATOM UTF-8 document + assertThat(response.getStatusCode(), equalTo(HttpStatus.OK)); + assertThat(response.getHeaders().getContentType().toString(), + equalTo("application/atomserv+xml;charset=UTF-8")); + + // Check for correct SWORD version in response body + assertThat(response.getBody(), + containsString("2.0")); + } + + @Test + public void collectionUnauthorizedTest() throws Exception { + // Attempt to POST to /collection endpoint without sending authentication information + ResponseEntity response = postResponseAsString(COLLECTION_PATH, null, null, null); + // Expect a 401 response code + assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + } + + @Test + @Ignore + public void collectionTest() throws Exception { + // TODO: Actually test collection endpoint via SWORDv2. + // Currently, we are just ensuring the /collection endpoint exists (see above) and isn't throwing a 404 + } + + @Test + public void mediaResourceUnauthorizedTest() throws Exception { + // Attempt to POST to /mediaresource endpoint without sending authentication information + ResponseEntity response = postResponseAsString(MEDIA_RESOURCE_PATH, null, null, null); + // Expect a 401 response code + assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + } + + @Test + @Ignore + public void mediaResourceTest() throws Exception { + // TODO: Actually test this endpoint via SWORDv2. + // Currently, we are just ensuring the /mediaresource endpoint exists (see above) and isn't throwing a 404 + } + + @Test + public void containerUnauthorizedTest() throws Exception { + // Attempt to POST to /container endpoint without sending authentication information + ResponseEntity response = postResponseAsString(CONTAINER_PATH, null, null, null); + // Expect a 401 response code + assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + } + + @Test + @Ignore + public void containerTest() throws Exception { + // TODO: Actually test this endpoint via SWORDv2. + // Currently, we are just ensuring the /container endpoint exists (see above) and isn't throwing a 404 + } + + @Test + public void statementUnauthorizedTest() throws Exception { + // Attempt to GET /statement endpoint without sending authentication information + ResponseEntity response = getResponseAsString(STATEMENT_PATH); + // Expect a 401 response code + assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); + } + + @Test + @Ignore + public void statementTest() throws Exception { + // TODO: Actually test this endpoint via SWORDv2. + // Currently, we are just ensuring the /statement endpoint exists (see above) and isn't throwing a 404 + } +} + diff --git a/dspace-spring-rest/src/test/java/org/dspace/core/HibernateTestUtil.java b/dspace-spring-rest/src/test/java/org/dspace/core/HibernateTestUtil.java new file mode 100644 index 0000000000..3f214daf34 --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/core/HibernateTestUtil.java @@ -0,0 +1,32 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.core; + +import java.sql.SQLException; + +import org.hibernate.Session; +import org.hibernate.SessionFactory; + +/** + * Helper class to execute Hibernate queries in the DSpace test classes + */ +public class HibernateTestUtil { + + private HibernateTestUtil() { + //private constructor required for utility classes + } + + public static Session getHibernateSession(Context context) throws SQLException { + return ((Session) context.getDBConnection().getSession()); + } + + public static SessionFactory getHibernateSessionFactory(Context context) throws SQLException { + return ((Session) context.getDBConnection().getSession()).getSessionFactory(); + } + +} diff --git a/dspace-spring-rest/src/test/resources/org/dspace/app/rest/test/metadata-patch-suite.json b/dspace-spring-rest/src/test/resources/org/dspace/app/rest/test/metadata-patch-suite.json index cff452fe91..a09ecf1ba9 100644 --- a/dspace-spring-rest/src/test/resources/org/dspace/app/rest/test/metadata-patch-suite.json +++ b/dspace-spring-rest/src/test/resources/org/dspace/app/rest/test/metadata-patch-suite.json @@ -23,7 +23,7 @@ ], "expect": { "dc.title": [ - { "value": "title 1", "language": null, "authority": null, "confidence": -1 } + { "value": "title 1", "language": null, "authority": null, "confidence": -1, "place": 0} ] } }, @@ -38,8 +38,8 @@ ], "expect": { "dc.title": [ - { "value": "title 1", "language": null, "authority": null, "confidence": -1 }, - { "value": "最後のタイトル", "language": "ja_JP", "authority": null, "confidence": -1 } + { "value": "title 1", "language": null, "authority": null, "confidence": -1,"place": 0 }, + { "value": "最後のタイトル", "language": "ja_JP", "authority": null, "confidence": -1 ,"place": 1} ] } }, @@ -56,9 +56,9 @@ ], "expect": { "dc.title": [ - { "value": "title 0", "language": null, "authority": null, "confidence": -1 }, - { "value": "title 1", "language": null, "authority": null, "confidence": -1 }, - { "value": "最後のタイトル", "language": "ja_JP", "authority": null, "confidence": -1 } + { "value": "title 0", "language": null, "authority": null, "confidence": -1 ,"place": 0 }, + { "value": "title 1", "language": null, "authority": null, "confidence": -1 ,"place": 1 }, + { "value": "最後のタイトル", "language": "ja_JP", "authority": null, "confidence": -1 ,"place": 2 } ] } }, @@ -73,9 +73,9 @@ ], "expect": { "dc.title": [ - { "value": "title 0", "language": null, "authority": null, "confidence": -1 }, - { "value": "最後のタイトル", "language": "ja_JP", "authority": null, "confidence": -1 }, - { "value": "title 1", "language": null, "authority": null, "confidence": -1 } + { "value": "title 0", "language": null, "authority": null, "confidence": -1 ,"place": 0 }, + { "value": "最後のタイトル", "language": "ja_JP", "authority": null, "confidence": -1 ,"place": 1 }, + { "value": "title 1", "language": null, "authority": null, "confidence": -1 ,"place": 2 } ] } }, @@ -95,9 +95,9 @@ ], "expect": { "dc.title": [ - { "value": "title 0", "language": null, "authority": null, "confidence": -1 }, - { "value": "title A", "language": "en_US", "authority": null, "confidence": -1 }, - { "value": "title 1", "language": null, "authority": null, "confidence": -1 } + { "value": "title 0", "language": null, "authority": null, "confidence": -1 ,"place": 0 }, + { "value": "title A", "language": "en_US", "authority": null, "confidence": -1 ,"place": 1 }, + { "value": "title 1", "language": null, "authority": null, "confidence": -1 ,"place": 2 } ] } }, @@ -112,10 +112,10 @@ ], "expect": { "dc.title": [ - { "value": "title 0", "language": null, "authority": null, "confidence": -1 }, - { "value": "title A", "language": "en_US", "authority": null, "confidence": -1 }, - { "value": "title 1", "language": null, "authority": null, "confidence": -1 }, - { "value": "title A", "language": "en_US", "authority": null, "confidence": -1 } + { "value": "title 0", "language": null, "authority": null, "confidence": -1 ,"place": 0 }, + { "value": "title A", "language": "en_US", "authority": null, "confidence": -1 ,"place": 1 }, + { "value": "title 1", "language": null, "authority": null, "confidence": -1 ,"place": 2 }, + { "value": "title A", "language": "en_US", "authority": null, "confidence": -1 ,"place": 3 } ] } }, @@ -133,8 +133,8 @@ ], "expect": { "dc.title": [ - { "value": "title 0", "language": null, "authority": null, "confidence": -1 }, - { "value": "title 1", "language": null, "authority": null, "confidence": -1 } + { "value": "title 0", "language": null, "authority": null, "confidence": -1 ,"place": 0 }, + { "value": "title 1", "language": null, "authority": null, "confidence": -1 ,"place": 1 } ] } }, diff --git a/dspace-sword/pom.xml b/dspace-sword/pom.xml index 63f149e01f..e40e62c601 100644 --- a/dspace-sword/pom.xml +++ b/dspace-sword/pom.xml @@ -1,12 +1,11 @@ - + 4.0.0 org.dspace dspace-sword - war + jar DSpace SWORD - DSpace SWORD Deposit Service Provider Web Application + DSpace SWORD Deposit Service Provider Extension - WEB-INF/lib/*.jar - WEB-INF/lib/*.jar - - true - - - - prepare-package - - - - - - oracle-support @@ -96,6 +73,19 @@ dspace-api-lang + + + org.springframework.boot + spring-boot-starter + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-logging + + + + jaxen jaxen diff --git a/dspace-sword/src/main/java/org/dspace/app/configuration/SWORDWebConfig.java b/dspace-sword/src/main/java/org/dspace/app/configuration/SWORDWebConfig.java new file mode 100644 index 0000000000..37b6d651a8 --- /dev/null +++ b/dspace-sword/src/main/java/org/dspace/app/configuration/SWORDWebConfig.java @@ -0,0 +1,88 @@ +/** + * 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.configuration; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.web.servlet.ServletContextInitializer; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * SWORD webapp configuration. Replaces the old web.xml + *

+ * This @Configuration class is automatically discovered by Spring via a @ComponentScan. + *

+ * All SWORD web configurations (beans) can be enabled or disabled by setting "sword-server.enabled" + * to true or false, respectively (in your DSpace configuration). Default is "false". + *

+ * All @Value annotated configurations below can also be overridden in your DSpace configuration. + * + * @author Tim Donohue + */ +@Configuration +public class SWORDWebConfig { + // Path where SWORD should be deployed (when enabled). Defaults to "sword" + @Value("${sword-server.path:sword}") + private String swordPath; + + // SWORD Server class. Defaults to "org.dspace.sword.DSpaceSWORDServer" + @Value("${sword-server.class:org.dspace.sword.DSpaceSWORDServer}") + private String serverClass; + + // SWORD Authentication Method. Defaults to "Basic" + @Value("${sword-server.authentication-method:Basic}") + private String authenticationMethod; + + /** + * Initialize all required Context Parameters (i.e. in web.xml), based on configurations above. + *

+ * This bean is only loaded when sword-server.enabled = true + * @return ServletContextInitializer which includes all required params + */ + @Bean + @ConditionalOnProperty("sword-server.enabled") + public ServletContextInitializer swordv1ContextInitializer() { + return servletContext -> { + servletContext.setInitParameter("sword-server-class", serverClass); + servletContext.setInitParameter("authentication-method", authenticationMethod); + }; + } + + // Servlet Beans. All of the below bean definitions map servlets to respond to specific URL patterns + // These are the combined equivalent of and in web.xml + // All beans are only loaded when sword-server.enabled = true + + @Bean + @ConditionalOnProperty("sword-server.enabled") + public ServletRegistrationBean swordv1ServiceDocumentBean() { + ServletRegistrationBean bean = new ServletRegistrationBean( new org.purl.sword.server.ServiceDocumentServlet(), + "/" + swordPath + "/servicedocument/*"); + bean.setLoadOnStartup(1); + return bean; + } + + @Bean + @ConditionalOnProperty("sword-server.enabled") + public ServletRegistrationBean swordv1DepositBean() { + ServletRegistrationBean bean = new ServletRegistrationBean( new org.purl.sword.server.DepositServlet(), + "/" + swordPath + "/deposit/*"); + bean.setLoadOnStartup(1); + return bean; + } + + @Bean + @ConditionalOnProperty("sword-server.enabled") + public ServletRegistrationBean swordv1MediaLinkBean() { + ServletRegistrationBean bean = new ServletRegistrationBean( new org.purl.sword.server.AtomDocumentServlet(), + "/" + swordPath + "/media-link/*"); + bean.setLoadOnStartup(1); + return bean; + } +} diff --git a/dspace-sword/src/main/java/org/purl/sword/server/AtomDocumentServlet.java b/dspace-sword/src/main/java/org/purl/sword/server/AtomDocumentServlet.java index adf049c10a..0b2a675efd 100644 --- a/dspace-sword/src/main/java/org/purl/sword/server/AtomDocumentServlet.java +++ b/dspace-sword/src/main/java/org/purl/sword/server/AtomDocumentServlet.java @@ -27,11 +27,6 @@ import org.purl.sword.base.SWORDException; */ public class AtomDocumentServlet extends DepositServlet { - public AtomDocumentServlet() - throws ServletException { - super(); - } - /** * Process the get request. */ diff --git a/dspace-sword/src/main/webapp/WEB-INF/web.xml b/dspace-sword/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 14b873ccd9..0000000000 --- a/dspace-sword/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - - - DSpace SWORD Server - - - - The location of the DSpace home directory - dspace.dir - ${dspace.dir} - - - - The SWORDServer class name - sword-server-class - org.dspace.sword.DSpaceSWORDServer - - - - log4jConfiguration - ${dspace.dir}/config/log4j2.xml - - The location of the Log4J configuration - - - - - The type of authentication used : [Basic|None] - authentication-method - Basic - - - - - - org.dspace.app.util.DSpaceContextListener - - - - - org.dspace.servicemanager.servlet.DSpaceKernelServletContextListener - - - - - - servicedocument - org.purl.sword.server.ServiceDocumentServlet - - - - deposit - org.purl.sword.server.DepositServlet - - - - media-link - org.purl.sword.server.AtomDocumentServlet - - - - - - servicedocument - /servicedocument/* - - - - deposit - /deposit/* - - - - media-link - /media-link/* - - - diff --git a/dspace-swordv2/pom.xml b/dspace-swordv2/pom.xml index 6ac1a15b5d..ec2539ef44 100644 --- a/dspace-swordv2/pom.xml +++ b/dspace-swordv2/pom.xml @@ -1,11 +1,10 @@ - + 4.0.0 org.dspace dspace-swordv2 - war + jar DSpace SWORD v2 - DSpace SWORD v2 Deposit Service Provider Web Application + DSpace SWORD v2 Deposit Service Provider Extension - WEB-INF/lib/*.jar - WEB-INF/lib/*.jar - - true - - - - prepare-package - - - - - - - oracle-support @@ -84,6 +60,8 @@ javax.servlet-api provided + + org.swordapp sword2-server @@ -99,26 +77,41 @@ javax.servlet servlet-api - - log4j + + log4j log4j - - - org.slf4j - slf4j-log4j12 - + + + org.slf4j + slf4j-log4j12 + + + + com.hp.hpl.jena + jena + - - org.swordapp - sword2-server - 1.0 - war - + + org.dspace dspace-api + + + + org.springframework.boot + spring-boot-starter + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-logging + + + + org.apache.logging.log4j log4j-api @@ -135,7 +128,6 @@ org.apache.abdera abdera-client 1.1.3 - org.apache.ws.commons.axiom diff --git a/dspace-swordv2/src/main/java/org/dspace/app/configuration/SWORDv2WebConfig.java b/dspace-swordv2/src/main/java/org/dspace/app/configuration/SWORDv2WebConfig.java new file mode 100644 index 0000000000..ece5094a61 --- /dev/null +++ b/dspace-swordv2/src/main/java/org/dspace/app/configuration/SWORDv2WebConfig.java @@ -0,0 +1,143 @@ +/** + * 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.configuration; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.web.servlet.ServletContextInitializer; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * SWORDv2 webapp configuration. Replaces the old web.xml + *

+ * This @Configuration class is automatically discovered by Spring via a @ComponentScan. + *

+ * All SWORDv2 web configurations (beans) can be enabled or disabled by setting "swordv2-server.enabled" + * to true or false, respectively (in your DSpace configuration). Default is "false". + *

+ * All @Value annotated configurations below can also be overridden in your DSpace configuration. + * + * @author Tim Donohue + */ +@Configuration +public class SWORDv2WebConfig { + // Path where SWORDv2 should be deployed (when enabled). Defaults to "swordv2" + @Value("${swordv2-server.path:swordv2}") + private String swordv2Path; + + // ServiceDocumentManager server implementation class name (default: org.dspace.sword2.ServiceDocumentManagerDSpace) + @Value("${swordv2-server.service-document-impl:org.dspace.sword2.ServiceDocumentManagerDSpace}") + private String serviceDocImpl; + + // CollectionListManager server implementation class name (default: org.dspace.sword2.CollectionListManagerDSpace) + @Value("${swordv2-server.collection-list-impl:org.dspace.sword2.CollectionListManagerDSpace}") + private String collectionListImpl; + + // CollectionDepositManager server implementation class name + // (default: org.dspace.sword2.CollectionDepositManagerDSpace) + @Value("${swordv2-server.collection-deposit-impl:org.dspace.sword2.CollectionDepositManagerDSpace}") + private String collectionDepositImpl; + + // MediaResourceManager server implementation class name (default: org.dspace.sword2.MediaResourceManagerDSpace) + @Value("${swordv2-server.media-resource-impl:org.dspace.sword2.MediaResourceManagerDSpace}") + private String mediaResourceImpl; + + // ContainerManager server implementation class name (default: org.dspace.sword2.ContainerManagerDSpace) + @Value("${swordv2-server.container-impl:org.dspace.sword2.ContainerManagerDSpace}") + private String containerImpl; + + // StatementManager server implementation class name (default: org.dspace.sword2.StatementManagerDSpace) + @Value("${swordv2-server.statement-impl:org.dspace.sword2.StatementManagerDSpace}") + private String statementImpl; + + // SwordConfiguration server implementation class name (default: org.dspace.sword2.SwordConfigurationDSpace) + @Value("${swordv2-server.config-impl:org.dspace.sword2.SwordConfigurationDSpace}") + private String configImpl; + + // Authentication Method. Defaults to "Basic" + @Value("${swordv2-server.auth-type:Basic}") + private String authenticationMethod; + + /** + * Initialize all required Context Parameters (i.e. in web.xml), based on configurations above. + *

+ * This bean is only loaded when swordv2-server.enabled = true + * @return ServletContextInitializer which includes all required params + */ + @Bean + @ConditionalOnProperty("swordv2-server.enabled") + public ServletContextInitializer swordv2ContextInitializer() { + return servletContext -> { + servletContext.setInitParameter("service-document-impl", serviceDocImpl); + servletContext.setInitParameter("collection-list-impl", collectionListImpl); + servletContext.setInitParameter("collection-deposit-impl", collectionDepositImpl); + servletContext.setInitParameter("media-resource-impl", mediaResourceImpl); + servletContext.setInitParameter("container-impl", containerImpl); + servletContext.setInitParameter("statement-impl", statementImpl); + servletContext.setInitParameter("config-impl", configImpl); + servletContext.setInitParameter("authentication-method", authenticationMethod); + }; + } + + // Servlet Beans. All of the below bean definitions map servlets to respond to specific URL patterns + // These are the combined equivalent of and in web.xml + // All beans are only loaded when swordv2-server.enabled = true + + @Bean + @ConditionalOnProperty("swordv2-server.enabled") + public ServletRegistrationBean swordv2ServiceDocumentBean() { + ServletRegistrationBean bean = + new ServletRegistrationBean(new org.swordapp.server.servlets.ServiceDocumentServletDefault(), + "/" + swordv2Path + "/servicedocument/*"); + bean.setLoadOnStartup(1); + return bean; + } + + @Bean + @ConditionalOnProperty("swordv2-server.enabled") + public ServletRegistrationBean swordv2CollectionBean() { + ServletRegistrationBean bean = + new ServletRegistrationBean( new org.swordapp.server.servlets.CollectionServletDefault(), + "/" + swordv2Path + "/collection/*"); + bean.setLoadOnStartup(1); + return bean; + } + + @Bean + @ConditionalOnProperty("swordv2-server.enabled") + public ServletRegistrationBean swordv2MediaResourceBean() { + ServletRegistrationBean bean = + new ServletRegistrationBean( new org.swordapp.server.servlets.MediaResourceServletDefault(), + "/" + swordv2Path + "/edit-media/*"); + bean.setLoadOnStartup(1); + return bean; + } + + @Bean + @ConditionalOnProperty("swordv2-server.enabled") + public ServletRegistrationBean swordv2ContainerBean() { + ServletRegistrationBean bean = + new ServletRegistrationBean( new org.swordapp.server.servlets.ContainerServletDefault(), + "/" + swordv2Path + "/edit/*"); + bean.setLoadOnStartup(1); + return bean; + } + + @Bean + @ConditionalOnProperty("swordv2-server.enabled") + public ServletRegistrationBean swordv2StatementBean() { + ServletRegistrationBean bean = + new ServletRegistrationBean( new org.swordapp.server.servlets.StatementServletDefault(), + "/" + swordv2Path + "/statement/*"); + bean.setLoadOnStartup(1); + return bean; + } +} + diff --git a/dspace-swordv2/src/main/webapp/WEB-INF/web.xml b/dspace-swordv2/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index f7794795dd..0000000000 --- a/dspace-swordv2/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,154 +0,0 @@ - - - - - DSpace SWORD 2.0 Server - - - - The location of the DSpace home directory - dspace.dir - ${dspace.dir} - - - - - - The ServiceDocumentManager server implementation class name - service-document-impl - org.dspace.sword2.ServiceDocumentManagerDSpace - - - - log4jConfiguration - ${dspace.dir}/config/log4j2.xml - - The location of the Log4J configuration - - - - - - The CollectionListManager server implementation class name - collection-list-impl - org.dspace.sword2.CollectionListManagerDSpace - - - - The CollectionDepositManager server implementation class name - collection-deposit-impl - org.dspace.sword2.CollectionDepositManagerDSpace - - - - The MediaResourceManager server implementation class name - media-resource-impl - org.dspace.sword2.MediaResourceManagerDSpace - - - - The ContainerManager server implementation class name - container-impl - org.dspace.sword2.ContainerManagerDSpace - - - - The StatementManager server implementation class name - statement-impl - org.dspace.sword2.StatementManagerDSpace - - - - - The SwordConfiguration server implementation class name - config-impl - org.dspace.sword2.SwordConfigurationDSpace - - - - The type of authentication used : [Basic|None] - authentication-method - Basic - - - - - - org.dspace.app.util.DSpaceContextListener - - - - - org.dspace.servicemanager.servlet.DSpaceKernelServletContextListener - - - - - - - servicedocument - org.swordapp.server.servlets.ServiceDocumentServletDefault - - - - collection - org.swordapp.server.servlets.CollectionServletDefault - - - - mediaresource - org.swordapp.server.servlets.MediaResourceServletDefault - - - - container - org.swordapp.server.servlets.ContainerServletDefault - - - - statement - org.swordapp.server.servlets.StatementServletDefault - - - - - - servicedocument - /servicedocument/* - - - - collection - /collection/* - - - - mediaresource - /edit-media/* - - - - container - /edit/* - - - - statement - /statement/* - - - diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 4076a1a689..7686ae1cc0 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -26,17 +26,18 @@ dspace.dir = /dspace dspace.hostname = localhost # DSpace base host URL. Include port number etc. -dspace.baseUrl = http://localhost:8080 +dspace.baseUrl = http://localhost:8080/spring-rest # 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. # # Alternatively, you can use a url redirect or deploy the web application under the servlet container root. +# This is the URL that the front-end will be served on 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}/spring-rest +dspace.restUrl = ${dspace.baseUrl} # Optional: DSpace URL for mobile access # This diff --git a/dspace/config/hibernate.cfg.xml b/dspace/config/hibernate.cfg.xml index 548022f1d4..85bbf15b8f 100644 --- a/dspace/config/hibernate.cfg.xml +++ b/dspace/config/hibernate.cfg.xml @@ -44,6 +44,10 @@ + + + + diff --git a/dspace/config/item-submission.xml b/dspace/config/item-submission.xml index 5756e9c4e8..d66fc202d5 100644 --- a/dspace/config/item-submission.xml +++ b/dspace/config/item-submission.xml @@ -57,6 +57,37 @@ org.dspace.app.rest.submit.step.DescribeStep submission-form + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + submit.progressbar.describe.steptwo org.dspace.app.rest.submit.step.DescribeStep @@ -150,6 +181,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/dspace/config/launcher.xml b/dspace/config/launcher.xml index 1b144d33e3..4c2afcdca4 100644 --- a/dspace/config/launcher.xml +++ b/dspace/config/launcher.xml @@ -373,4 +373,11 @@ org.dspace.app.util.Version + + initialize-entities + Initialize the entities with a provided xml + + org.dspace.app.util.InitializeEntities + + diff --git a/dspace/config/modules/discovery.cfg b/dspace/config/modules/discovery.cfg index 27435be08a..a310182f10 100644 --- a/dspace/config/modules/discovery.cfg +++ b/dspace/config/modules/discovery.cfg @@ -18,11 +18,20 @@ discovery.search.server = ${solr.server}/search # discovery.index.ignore-authority = false discovery.index.projection=dc.title,dc.contributor.*,dc.date.issued +# Allow auto-reindexing. +# If any database migrations are applied to your database (via Flyway), then a +# reindex flag is always written to '[dspace]/solr/search/conf/reindex.flag'. +# Whenever the DSpace webapp is (re)started, it checks whether the autoReindex +# property is enabled AND that such a file exists. If the two conditions are +# satisfied, a background reindex of all content is triggered in Discovery. +# Defaults to true: auto-reindexing is enabled. +#discovery.autoReindex = true + # Value used for the namedresourcetype facet used by the mydspace # \n|||\n### -# the separator between the sort-value and the display-value \n|||\n must +# the separator between the sort-value and the display-value \n|||\n must # match the value of the discovery.solr.facets.split.char defined above -# the sort-value can be used to force a fixed order for the facet if it is +# the sort-value can be used to force a fixed order for the facet if it is # configured in the discovery.xml to be sorted by value discovery.facet.namedtype.item = 000item\n|||\nArchived###item discovery.facet.namedtype.workspace = 001workspace\n|||\nWorkspace###workspace diff --git a/dspace/config/modules/oai.cfg b/dspace/config/modules/oai.cfg index 493eb2510e..0765331c58 100644 --- a/dspace/config/modules/oai.cfg +++ b/dspace/config/modules/oai.cfg @@ -4,12 +4,24 @@ # These configs are used by the OAI-PMH interface # #---------------------------------------------------------------# +# Whether or not to enable the OAI module +# When "true", the OAI module is accessible on ${oai.path} +# When "false" or commented out, OAI is disabled/inaccessible. +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +#oai.enabled = true + +# Path where OAI module is available +# Defaults to "oai", which means the OAI module would be available +# at ${dspace.restURL}/oai/ +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +oai.path = oai + # Storage: solr | database (solr is recommended) oai.storage=solr # The base URL of the OAI webapp (do not include the context e.g. /request, /openaire, etc). # Note: Comment out if you want to fallback to the request's URL. -oai.url = ${dspace.baseUrl}/oai +oai.url = ${dspace.baseUrl}/${oai.path} # Base solr index oai.solr.url=${solr.server}/oai diff --git a/dspace/config/modules/rdf.cfg b/dspace/config/modules/rdf.cfg index 355d9f65c0..f33a320360 100644 --- a/dspace/config/modules/rdf.cfg +++ b/dspace/config/modules/rdf.cfg @@ -1,12 +1,29 @@ -# These configs are used by dspace-rdf and the buildin Linked Data export (rdfizer) +#---------------------------------------------------------------# +#----------------RDF (LINKED DATA) CONFIGURATIONS---------------# +#---------------------------------------------------------------# +# These configs are only used by the RDF (Linked Data) # +# interface (provided by dspace-rdf and the built-in Linked # +# Data export (rdfizer)). +#---------------------------------------------------------------# + +# Whether or not to enable the RDF module +# When "true", the RDF module is accessible on ${rdf.path} +# When "false" or commented out, RDF is disabled/inaccessible. +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +#rdf.enabled = true + +# Path where RDF module is available (in the Spring REST webapp) +# Defaults to "rdf", which means the RDF module would be available +# at ${dspace.restURL}/rdf/ +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +rdf.path = rdf # Configure if content negotiation should be enabled rdf.contentNegotiation.enable = false -# Set the url of the dspace-rdf module here. This is necessary to use content +# Set the url of the RDF module here. This is necessary to use content # negotiation -rdf.contextPath = ${dspace.baseUrl}/rdf - +rdf.contextPath = ${dspace.baseUrl}/${rdf.path} # Address of the public SPARQL endpoint diff --git a/dspace/config/modules/rest.cfg b/dspace/config/modules/rest.cfg index c8ac235dc9..2d1149c4d3 100644 --- a/dspace/config/modules/rest.cfg +++ b/dspace/config/modules/rest.cfg @@ -1,9 +1,17 @@ #---------------------------------------------------------------# #--------------------REST CONFIGURATIONS------------------------# #---------------------------------------------------------------# -# These configs are used by the REST module # +# These configs are used by the RESTv7 module # #---------------------------------------------------------------# +# Allowed CORS origins. Defaults to * (everywhere) +# Multiple allowed origin URLs may be comma separated +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +rest.cors.allowed-origins = * + +#---------------------------------------------------------------# +# These configs are used by the deprecated REST (v4-6) module # +#---------------------------------------------------------------# # record stats in DSpace statistics module rest.stats = true @@ -137,4 +145,4 @@ rest.report-regex-xml-entity = ^.*&#.*$ rest.report-regex-non-ascii = ^.*[^\\p{ASCII}].*$ # The maximum number of results to return for 1 request -rest.search.max.results = 100 \ No newline at end of file +rest.search.max.results = 100 diff --git a/dspace/config/modules/sword-server.cfg b/dspace/config/modules/sword-server.cfg index 6c6778d9c7..0da35ba881 100644 --- a/dspace/config/modules/sword-server.cfg +++ b/dspace/config/modules/sword-server.cfg @@ -6,6 +6,28 @@ # SWORD protocol) # #---------------------------------------------------------------# +# Whether or not to enable the SWORD v1 module +# When "true", the SWORD module is accessible on ${sword-server.path} +# When "false" or commented out, SWORD is disabled/inaccessible. +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +#sword-server.enabled = true + +# Path where SWORD v1 module is available (in the Spring REST webapp) +# Defaults to "sword", which means the SWORD mould would be available +# at ${dspace.restURL}/sword/ +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +#sword-server.path = sword + +# The SWORDServer class name (in charge of all SWORD activities) +# This Java class must implement 'org.purl.sword.server.SWORDServer' +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +#sword-server.class = org.dspace.sword.DSpaceSWORDServer + +# The Authentication Method SWORD should use. +# Valid values are "Basic" or "None". Default is "Basic" +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +#sword-server.authentication-method = Basic + # tell the SWORD METS implementation which package ingester to use # to install deposited content. This should refer to one of the # classes configured for: diff --git a/dspace/config/modules/swordv2-server.cfg b/dspace/config/modules/swordv2-server.cfg index 6e6cfd1c39..200d100f8b 100644 --- a/dspace/config/modules/swordv2-server.cfg +++ b/dspace/config/modules/swordv2-server.cfg @@ -6,6 +6,18 @@ # SWORD 2.0 protocol) # #---------------------------------------------------------------# +# Whether or not to enable the SWORD v2 module +# When "true", the SWORD v2 module is accessible on ${swordv2-server.path} +# When "false" or commented out, SWORD v2 is disabled/inaccessible. +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +#swordv2-server.enabled = true + + # Path where SWORD v2 module is available (in the Spring REST webapp) +# Defaults to "swordv2", which means the SWORD v2 module would be available +# at ${dspace.restURL}/swordv2/ +# (Requires reboot of servlet container, e.g. Tomcat, to reload) +#swordv2-server.path = swordv2 + # the base url of the sword 2.0 system # # the default if {dspace.url}/swordv2 @@ -194,6 +206,32 @@ swordv2-server.generator.version = 2.0 # Other valid values: 'None' swordv2-server.auth-type = Basic +# SWORD v2 server implementation classes +# These are the default implementation classes to use for SWORD functions. +# Uncomment if you wish to change the implementation class. +# (Requires reboot of servlet container, e.g. Tomcat, to reload these settings) +# +# ServiceDocumentManager server implementation +#swordv2-server.service-document-impl = org.dspace.sword2.ServiceDocumentManagerDSpace +# +# CollectionListManager server implementation class +#swordv2-server.collection-list-impl = org.dspace.sword2.CollectionListManagerDSpace +# +# CollectionDepositManager server implementation class +#swordv2-server.collection-deposit-impl = org.dspace.sword2.CollectionDepositManagerDSpace +# +# MediaResourceManager server implementation class +#swordv2-server.media-resource-impl = org.dspace.sword2.MediaResourceManagerDSpace +# +# ContainerManager server implementation class name +#swordv2-server.container-impl = org.dspace.sword2.ContainerManagerDSpace +# +# StatementManager server implementation class +#swordv2-server.statement-impl = org.dspace.sword2.StatementManagerDSpace +# +# SwordConfiguration server implementation class +#swordv2-server.config-impl = org.dspace.sword2.SwordConfigurationDSpace + # The location where uploaded files and packages are # stored while being processed swordv2-server.upload.tempdir = ${upload.temp.dir} diff --git a/dspace/config/registries/journal-types.xml b/dspace/config/registries/journal-types.xml new file mode 100644 index 0000000000..57a40a3a72 --- /dev/null +++ b/dspace/config/registries/journal-types.xml @@ -0,0 +1,55 @@ + + + + DSpace journal Types + + + + journal + http://dspace.org/journal + + + + + journal + contributor + editor + + + + + + journal + publisher + + + + + + journal + identifier + issn + + + + journal + identifier + name + + + + + journal + identifier + description + + + + + journal + title + + + + + diff --git a/dspace/config/registries/journalissue-types.xml b/dspace/config/registries/journalissue-types.xml new file mode 100644 index 0000000000..fd53102128 --- /dev/null +++ b/dspace/config/registries/journalissue-types.xml @@ -0,0 +1,46 @@ + + + + DSpace journalissue Types + + + + journalissue + http://dspace.org/journalissue + + + + journalissue + issuedate + + + + + + + journalissue + identifier + number + + + + + journalissue + identifier + name + + + + + journalissue + identifier + description + + + + journalissue + identifier + keyword + + + diff --git a/dspace/config/registries/journalvolume-types.xml b/dspace/config/registries/journalvolume-types.xml new file mode 100644 index 0000000000..a3a02e554e --- /dev/null +++ b/dspace/config/registries/journalvolume-types.xml @@ -0,0 +1,41 @@ + + + + DSpace journalvolume Types + + + + journalvolume + http://dspace.org/journalvolume + + + + journalvolume + issuedate + + + + + + + journalvolume + identifier + volume + + + + + journalvolume + identifier + name + + + + + journalvolume + identifier + description + + + + diff --git a/dspace/config/registries/orgunit-types.xml b/dspace/config/registries/orgunit-types.xml new file mode 100644 index 0000000000..14c2696e79 --- /dev/null +++ b/dspace/config/registries/orgunit-types.xml @@ -0,0 +1,55 @@ + + + + DSpace OrgUnit Types + + + + orgunit + http://dspace.org/orgunit + + + + + orgunit + identifier + name + + + + + orgunit + identifier + id + + + + + orgunit + identifier + dateestablished + + + + + orgunit + identifier + city + + + + + orgunit + identifier + country + + + + + orgunit + identifier + description + + + + diff --git a/dspace/config/registries/person-types.xml b/dspace/config/registries/person-types.xml new file mode 100644 index 0000000000..63947ff366 --- /dev/null +++ b/dspace/config/registries/person-types.xml @@ -0,0 +1,68 @@ + + + + DSpace Person Types + + + + person + http://dspace.org/person + + + + + person + identifier + lastname + + + + + person + identifier + firstname + + + + + person + identifier + email + + + + + person + identifier + orcid + + + + + person + identifier + birthdate + + + + + person + identifier + staffid + + + + + person + identifier + jobtitle + + + + + person + contributor + other + + + diff --git a/dspace/config/registries/project-types.xml b/dspace/config/registries/project-types.xml new file mode 100644 index 0000000000..866bb85439 --- /dev/null +++ b/dspace/config/registries/project-types.xml @@ -0,0 +1,75 @@ + + + + DSpace Project Types + + + + project + http://dspace.org/project + + + + + project + identifier + name + + + + + project + identifier + id + + + + + project + identifier + status + + + + + project + identifier + startdate + + + + + project + identifier + expectedcompletion + + + + + project + identifier + keyword + + + + + project + identifier + description + + + + + project + contributor + other + + + + + project + contributor + author + + + diff --git a/dspace/config/registries/relationship-formats.xml b/dspace/config/registries/relationship-formats.xml new file mode 100644 index 0000000000..ddc8296ddb --- /dev/null +++ b/dspace/config/registries/relationship-formats.xml @@ -0,0 +1,124 @@ + + + + DSpace Relationship + + + + relationship + http://dspace.org/relationship + + + + relation + http://dspace.org/relation + + + + relationship + type + Metadata field used for the type of entity, stored in the item + + + + relation + isAuthorOfPublication + + + + + relation + isPublicationOfAuthor + + + + relation + isProjectOfPublication + + + + relation + isPublicationOfProject + + + + relation + isOrgUnitOfPublication + + + + relation + isPublicationOfOrgUnit + + + + relation + isProjectOfPerson + + + + relation + isPersonOfProject + + + + relation + isOrgUnitOfPerson + + + + relation + isPersonOfOrgUnit + + + + relation + isOrgUnitOfProject + + + + relation + isProjectOfOrgUnit + + + + + + relation + isVolumeOfJournal + + + + relation + isJournalOfVolume + + + + relation + isIssueOfJournalVolume + + + + relation + isJournalVolumeOfIssue + + + + relation + isJournalOfPublication + + + + + relation + isJournalIssueOfPublication + + + + + + relation + isPublicationOfJournalIssue + + + diff --git a/dspace/config/spring/api/core-dao-services.xml b/dspace/config/spring/api/core-dao-services.xml index cc9244015e..bed3009f07 100644 --- a/dspace/config/spring/api/core-dao-services.xml +++ b/dspace/config/spring/api/core-dao-services.xml @@ -28,6 +28,9 @@ + + + diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index 280d8ba185..43959b25fd 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -1,7 +1,8 @@ - + @@ -49,6 +50,10 @@ + + + + @@ -107,5 +112,5 @@ - + diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index fa4b4f956a..4d41ad7c29 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -106,6 +106,7 @@ + @@ -120,6 +121,12 @@ + + + + + + @@ -137,7 +144,7 @@ - @@ -162,11 +169,19 @@ - + - + + + + + + + + + @@ -174,12 +189,24 @@ + + + + @@ -220,6 +247,7 @@ + @@ -234,6 +262,12 @@ + + + + + + @@ -251,7 +285,7 @@ - @@ -273,24 +307,44 @@ - + - + + + + + + + + + + + + + @@ -336,7 +390,7 @@ - @@ -384,7 +438,7 @@ - + @@ -413,7 +467,7 @@ - @@ -480,68 +534,68 @@ - + - + - - - + - + - + - + - + - + - - - + - + - + - + - + @@ -556,6 +610,62 @@ + + + + + relation.isAuthorOfPublication + + + + + + + + + + + relation.isProjectOfPublication + + + + + + + + + + + + relation.isOrgUnitOfPublication + + + + + + + + + + + relation.isPublicationOfJournalIssue + + + + + + + + + + + relation.isJournalOfPublication + + + + + + @@ -570,6 +680,19 @@ + + + + + + + relationship.type + + + + + + @@ -639,7 +762,7 @@ dc.type - + diff --git a/dspace/config/spring/api/virtual-metadata.xml b/dspace/config/spring/api/virtual-metadata.xml new file mode 100644 index 0000000000..9367a26b31 --- /dev/null +++ b/dspace/config/spring/api/virtual-metadata.xml @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + person.identifier.lastname + person.identifier.firstname + orgunit.identifier.name + + + + , + + + + + + + + + + + + orgunit.identifier.name + + + + + + + + + + + + person.identifier.lastname + person.identifier.firstname + + + + , + + + + + + + + + + + orgunit.identifier.name + + + + + + + + + + + orgunit.identifier.name + + + + + + + + + + + + + + journalvolume.identifier.volume + + + + + + + + + + + + + + + + + + + + + + + + journal.identifier.issn + + + + + + + journal.identifier.name + + + + + + + + + + + + + + journalissue.identifier.number + + + + + + + + + + + + + + journalvolume.identifier.name + + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace/config/submission-forms.xml b/dspace/config/submission-forms.xml index feefeaae4e..432b0b48c6 100644 --- a/dspace/config/submission-forms.xml +++ b/dspace/config/submission-forms.xml @@ -45,8 +45,8 @@ - - + +

@@ -175,7 +175,7 @@ it, please enter the types and the actual numbers or codes.
- +
@@ -229,6 +229,388 @@ it, please enter the types and the actual numbers or codes.
+
+ + + + person + identifier + lastname + + textarea + Enter the last name of the person + + + + + + person + identifier + firstname + + textarea + Enter the first name of the person + + + + + + person + identifier + email + + textarea + Enter the email of the person + + + + + + person + identifier + orcid + + textarea + Enter the orcid of the person + + + + + + person + identifier + birthdate + + date + Enter the birth date of the person + + + + + + person + identifier + staffid + + textarea + Enter the staff id of the person + + + + + + person + identifier + jobtitle + + textarea + Enter the job title of the person + + +
+ + + + +
+ + + + project + identifier + name + + textarea + Enter the name of the project + + + + + + project + identifier + id + + textarea + Enter the id of the project + + + + + + project + identifier + status + + textarea + Enter the status of the project + + + + + + project + identifier + startdate + + date + Enter the start date of the project + + + + + project + identifier + expectedcompletion + + date + Enter the expected completion date of the project + + + + + project + identifier + keyword + + textarea + Enter the keywords of the project + + + + + project + identifier + description + + textarea + Enter the description of the project + + +
+ +
+ + + orgunit + identifier + name + + textarea + Enter the name of the orgunit + + + + + orgunit + identifier + id + + textarea + Enter the id of the orgunit + + + + + orgunit + identifier + dateestablished + + date + Enter the established date of the orgunit + + + + + orgunit + identifier + city + + textarea + Enter the city of the orgunit + + + + + orgunit + identifier + country + + textarea + Enter the country of the orgunit + + + + + orgunit + identifier + description + + textarea + Enter the description of the orgunit + + +
+ +
+ + + journal + identifier + name + + textarea + Enter the name of the journal + + + + + + journal + contributor + editor + + textarea + Enter the editor of the journal + + + + + + journal + identifier + issn + + textarea + Enter the issn of the journal + + + + + + journal + identifier + description + + textarea + Enter the description of the journal + + + + + + journal + publisher + + textarea + Enter the publisher of the journal + + + +
+ +
+ + + journalvolume + identifier + name + + textarea + Enter the name of the journal volume + + + + + + journalvolume + identifier + volume + + textarea + Enter the volume of the journal volume + + + + + + journalvolume + issuedate + + date + Enter the issue date of the journal volume + + + + + + journalvolume + identifier + description + + textarea + Enter the description of the journal volume + + +
+ +
+ + + journalissue + identifier + name + + textarea + Enter the name of the journal issue + + + + + + journalissue + identifier + number + + textarea + Enter the number of the journal issue + + + + + + journalissue + issuedate + + date + Enter the issue date of the journal issue + + + + + + journalissue + identifier + description + + textarea + Enter the description of the journal issue + + + + + + journalissue + identifier + keyword + + textarea + Enter the keywords of the journal issue + + + +
+ diff --git a/dspace/modules/oai/pom.xml b/dspace/modules/oai/pom.xml deleted file mode 100644 index e36adfafcc..0000000000 --- a/dspace/modules/oai/pom.xml +++ /dev/null @@ -1,169 +0,0 @@ - - 4.0.0 - org.dspace.modules - oai - war - DSpace OAI-PMH :: Local Customizations - - This project allows you to overlay your own local OAI customizations - on top of the default OAI-PMH web application provided with DSpace. - - - - modules - org.dspace - 7.0-SNAPSHOT - .. - - - - - ${basedir}/../../.. - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - unpack - prepare-package - - unpack-dependencies - - - org.dspace.modules - additions - - ${project.build.directory}/additions - META-INF/** - - - - - - org.apache.maven.plugins - maven-war-plugin - - false - - true - - - - ${project.build.directory}/additions - WEB-INF/classes - - - - - - prepare-package - - - - - - - - - oracle-support - - - db.name - oracle - - - - - com.oracle - ojdbc6 - - - - - - - - org.dspace.modules - additions - - - com.lyncode - builder-commons - - - - - org.dspace - dspace-oai - war - - - org.dspace - dspace-oai - jar - classes - - - com.lyncode - builder-commons - - - com.google.guava - guava - - - - - javax.servlet - javax.servlet-api - provided - - - - - com.lyncode - builder-commons - 1.0.2 - - - com.google.guava - guava - - - - - - - - org.hamcrest - hamcrest-all - compile - - - - - - lyncode - dspace@lyncode.com - DSpace @ Lyncode - http://www.lyncode.com - - - diff --git a/dspace/modules/oai/src/main/webapp/.gitignore b/dspace/modules/oai/src/main/webapp/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/dspace/modules/rdf/pom.xml b/dspace/modules/rdf/pom.xml deleted file mode 100644 index 297d165a6d..0000000000 --- a/dspace/modules/rdf/pom.xml +++ /dev/null @@ -1,112 +0,0 @@ - - 4.0.0 - org.dspace.modules - rdf - war - DSpace RDF :: Local Customizations - - Overlay RDF customizations - - - - modules - org.dspace - 7.0-SNAPSHOT - .. - - - - - ${basedir}/../../.. - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - unpack - prepare-package - - unpack-dependencies - - - org.dspace.modules - additions - - ${project.build.directory}/additions - META-INF/** - - - - - - org.apache.maven.plugins - maven-war-plugin - - false - - true - - - - ${project.build.directory}/additions - WEB-INF/classes - - - - - - prepare-package - - - - - - - - - oracle-support - - - db.name - oracle - - - - - com.oracle - ojdbc6 - - - - - - - - org.dspace.modules - additions - - - org.dspace - dspace-rdf - war - - - javax.servlet - javax.servlet-api - provided - - - - diff --git a/dspace/modules/rdf/src/main/webapp/.gitignore b/dspace/modules/rdf/src/main/webapp/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/dspace/modules/sword/pom.xml b/dspace/modules/sword/pom.xml deleted file mode 100644 index a8498483ce..0000000000 --- a/dspace/modules/sword/pom.xml +++ /dev/null @@ -1,129 +0,0 @@ - - 4.0.0 - org.dspace.modules - sword - war - DSpace SWORD :: Local Customizations - - This project allows you to overlay your own local SWORD customizations - on top of the default SWORD web application provided with DSpace. - - - - - org.dspace - modules - 7.0-SNAPSHOT - .. - - - - - ${basedir}/../../.. - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - unpack - prepare-package - - unpack-dependencies - - - org.dspace.modules - additions - - ${project.build.directory}/additions - META-INF/** - - - - - - org.apache.maven.plugins - maven-war-plugin - - false - - true - - - - ${project.build.directory}/additions - WEB-INF/classes - - - - - - prepare-package - - - - - - - - - oracle-support - - - db.name - oracle - - - - - com.oracle - ojdbc6 - - - - - - - - org.dspace.modules - additions - - - xml-apis - xml-apis - - - - - org.dspace - dspace-sword - war - - - org.dspace - dspace-sword - jar - classes - - - javax.servlet - javax.servlet-api - provided - - - - diff --git a/dspace/modules/sword/src/main/webapp/.gitignore b/dspace/modules/sword/src/main/webapp/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/dspace/modules/swordv2/pom.xml b/dspace/modules/swordv2/pom.xml deleted file mode 100644 index 11f0b684b5..0000000000 --- a/dspace/modules/swordv2/pom.xml +++ /dev/null @@ -1,144 +0,0 @@ - - 4.0.0 - org.dspace.modules - swordv2 - war - DSpace SWORD v2 :: Local Customizations - - This project allows you to overlay your own local SWORD v2 customizations - on top of the default SWORD v2 web application provided with DSpace. - - - - - org.dspace - modules - 7.0-SNAPSHOT - .. - - - - - ${basedir}/../../.. - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - unpack - prepare-package - - unpack-dependencies - - - org.dspace.modules - additions - - ${project.build.directory}/additions - META-INF/** - - - - - - org.apache.maven.plugins - maven-war-plugin - - false - - true - - - - ${project.build.directory}/additions - WEB-INF/classes - - - - - - prepare-package - - - - - - - - - oracle-support - - - db.name - oracle - - - - - com.oracle - ojdbc6 - - - - - postgres-support - - - !db.name - - - - - org.postgresql - postgresql - - - - - - - - - org.dspace.modules - additions - - - - - org.dspace - dspace-swordv2 - war - - - - - org.dspace - dspace-swordv2 - jar - classes - - - - javax.servlet - javax.servlet-api - provided - - - - - diff --git a/dspace/modules/swordv2/src/main/webapp/.gitignore b/dspace/modules/swordv2/src/main/webapp/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/dspace/pom.xml b/dspace/pom.xml index 5961bf9a1c..bf46f313bf 100644 --- a/dspace/pom.xml +++ b/dspace/pom.xml @@ -198,13 +198,11 @@ org.dspace dspace-oai - classes compile org.dspace dspace-rdf - war compile @@ -227,13 +225,11 @@ org.dspace dspace-sword - classes compile org.dspace dspace-swordv2 - classes compile @@ -290,8 +286,6 @@ org.dspace dspace-oai - jar - classes com.lyncode diff --git a/dspace/solr/search/conf/schema.xml b/dspace/solr/search/conf/schema.xml index 407cb8842c..1d98a94c39 100644 --- a/dspace/solr/search/conf/schema.xml +++ b/dspace/solr/search/conf/schema.xml @@ -273,6 +273,9 @@ + + + diff --git a/dspace/src/main/docker/test/solr_web.xml b/dspace/src/main/docker/test/solr_web.xml deleted file mode 100644 index 50a8bd5b9a..0000000000 --- a/dspace/src/main/docker/test/solr_web.xml +++ /dev/null @@ -1,219 +0,0 @@ - - - - - - - - - - Solr home: configuration, cores etc. - solr/home - ${dspace.dir}/solr - java.lang.String - - - - - - - URL locating a Log4J configuration file (properties or XML). - - log4jConfiguration - ${dspace.dir}/config/log4j-solr.xml - - - - org.apache.logging.log4j.web.Log4jServletContextListener - - - - Activate logging - log4jServletFilter - org.apache.logging.log4j.web.Log4jServletFilter - - - - - LocalHostRestrictionFilter - org.dspace.solr.filters.LocalHostRestrictionFilter - - - - - SolrRequestFilter - org.apache.solr.servlet.SolrDispatchFilter - - - - - - log4jServletFilter - /* - REQUEST - FORWARD - INCLUDE - ERROR - - - - - - - SolrRequestFilter - /* - - - - - - Zookeeper - org.apache.solr.servlet.ZookeeperInfoServlet - - - - LoadAdminUI - org.apache.solr.servlet.LoadAdminUiServlet - - - - - - RedirectOldAdminUI - org.apache.solr.servlet.RedirectServlet - - destination - ${context}/#/ - - - - - RedirectOldZookeeper - org.apache.solr.servlet.RedirectServlet - - destination - ${context}/zookeeper - - - - - RedirectLogging - org.apache.solr.servlet.RedirectServlet - - destination - ${context}/#/~logging - - - - - SolrRestApi - org.restlet.ext.servlet.ServerServlet - - org.restlet.application - org.apache.solr.rest.SolrRestApi - - - - - RedirectOldAdminUI - /admin/ - - - RedirectOldAdminUI - /admin - - - RedirectOldZookeeper - /zookeeper.jsp - - - RedirectLogging - /logging - - - - - Zookeeper - /zookeeper - - - - LoadAdminUI - /admin.html - - - - SolrRestApi - /schema/* - - - - .xsl - - application/xslt+xml - - - - admin.html - - - diff --git a/pom.xml b/pom.xml index a06a94dfa6..fe79dd3ccc 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,8 @@ 1.8 3.17 42.2.1 + 2.13.0 2.6.2 1.7.22 @@ -37,6 +39,7 @@ https://issues.apache.org/jira/browse/SOLR-12858 --> 7.3.1 4.3.6.RELEASE + 1.4.4.RELEASE ${basedir} @@ -941,40 +944,16 @@ org.dspace dspace-sword 7.0-SNAPSHOT - jar - classes - - - org.dspace - dspace-sword - 7.0-SNAPSHOT - war org.dspace dspace-swordv2 7.0-SNAPSHOT - jar - classes - - - org.dspace - dspace-swordv2 - 7.0-SNAPSHOT - war org.dspace dspace-oai 7.0-SNAPSHOT - jar - classes - - - org.dspace - dspace-oai - 7.0-SNAPSHOT - war org.dspace @@ -985,7 +964,6 @@ org.dspace dspace-rdf 7.0-SNAPSHOT - war org.dspace