diff --git a/dspace-api/src/main/java/org/dspace/content/edit/AssociateItem.java b/dspace-api/src/main/java/org/dspace/content/edit/AssociateItem.java new file mode 100644 index 000000000000..1ef6c8c72efa --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/edit/AssociateItem.java @@ -0,0 +1,66 @@ +/** + * 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.edit; + +import org.dspace.content.Item; +import org.dspace.core.Context; + +/** + * Class representing an item in the process of associate item triggered by a user. + * It is some wrapper if the {@AssociateItemMode} and the both items where the mode interacts. + * + * @author Danilo Di Nuzzo (danilo.dinuzzo at 4science.it) + * @author Florian Gantner (florian.gantner@uni-bamberg.de) + * + */ +public class AssociateItem { + + private Item sourceitem; + private Item targetitem; + private AssociateItemMode mode; + private Context context; + + public AssociateItem(Context context, Item sourceitem, Item targetitem, AssociateItemMode mode) { + this.context = context; + this.sourceitem = sourceitem; + this.mode = mode; + this.targetitem = targetitem; + } + + public AssociateItemMode getMode() { + return mode; + } + + public void setMode(AssociateItemMode mode) { + this.mode = mode; + } + + public Item getTargetitem() { + return targetitem; + } + + public void setTargetitem(Item targetitem) { + this.targetitem = targetitem; + } + + public Item getSourceitem() { + return sourceitem; + } + + public void setSourceitem(Item sourceitem) { + this.sourceitem = sourceitem; + } + + public Context getContext() { + return context; + } + + public void setContext(Context context) { + this.context = context; + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/edit/AssociateItemMode.java b/dspace-api/src/main/java/org/dspace/content/edit/AssociateItemMode.java new file mode 100644 index 000000000000..314407d1cb42 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/edit/AssociateItemMode.java @@ -0,0 +1,207 @@ +/** + * 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.edit; + +import java.util.List; + +import org.dspace.content.logic.DefaultFilter; +import org.dspace.content.logic.Filter; +import org.dspace.content.security.AccessItemMode; +import org.dspace.content.security.CrisSecurity; + +/** + * This Class representing a modality of edit an item and adding some association to the item + * + * @author Danilo Di Nuzzo (danilo.dinuzzo at 4science.it) + * @author Florian Gantner (florian.gantner@uni-bamberg.de) + */ +public class AssociateItemMode implements AccessItemMode { + + public static final String NONE = "none"; + /** + * Configuration name + */ + private String name; + + /** + * Discovery name + */ + private String discovery; + + /** + * item Type Source + */ + private String itemTypeSource; + + /** + * item Type Target + */ + private String itemTypeTarget; + + /** + * Item type Metadatafield + */ + + private String metadatafield; + + /** + * Contains the condition for the item to be matched to fulfill to edit the source item + */ + private DefaultFilter conditionSource = null; + + /** + * Contains the condition for the item to be matched to fulfill to edit the target item + */ + private DefaultFilter conditionTarget = null; + + /** + * disable authorization check for editing source item + */ + private boolean disableAuthSource = false; + + /** + * Label used in UI for i18n + */ + private String label; + /** + * Defines the users enabled to use this edit configuration + */ + private List securities; + + /** + * Contains the list of groups metadata for CUSTOM security or groups name/uuid + * for GROUP security + */ + private List groups; + /** + * Contains the list of users metadata for CUSTOM security + */ + private List users; + /** + * Contains the list of items metadata for CUSTOM security + */ + private List items; + + public AssociateItemMode() {} + + @Override + public List getSecurities() { + return securities; + } + + public void setSecurity(CrisSecurity security) { + this.securities = List.of(security); + } + + public void setSecurities(List securities) { + this.securities = securities; + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public String getLabel() { + return label; + } + public void setLabel(String label) { + this.label = label; + } + public List getGroupMetadataFields() { + return groups; + } + public void setGroups(List groups) { + this.groups = groups; + } + public List getUserMetadataFields() { + return users; + } + public void setUsers(List users) { + this.users = users; + } + public List getItemMetadataFields() { + return items; + } + public void setItems(List items) { + this.items = items; + } + + @Override + public List getGroups() { + return groups; + } + + @Override + public Filter getAdditionalFilter() { + return null; + } + + @Override + public String toString() { + return "AssociateItemMode [name=" + name + ", label=" + label + ", security=" + securities + ", discovery=" + + discovery + "]"; + } + + public boolean isdisableAuthSource() { + return disableAuthSource; + } + + public DefaultFilter getConditionTarget() { + return conditionTarget; + } + + public void setConditionTarget(DefaultFilter conditionTarget) { + this.conditionTarget = conditionTarget; + } + + public DefaultFilter getConditionSource() { + return conditionSource; + } + + public void setConditionSource(DefaultFilter conditionSource) { + this.conditionSource = conditionSource; + } + + public String getItemTypeTarget() { + return itemTypeTarget; + } + + public void setItemTypeTarget(String itemTypeTarget) { + this.itemTypeTarget = itemTypeTarget; + } + + public String getItemTypeSource() { + return itemTypeSource; + } + + public void setItemTypeSource(String itemTypeSource) { + this.itemTypeSource = itemTypeSource; + } + + public void setDiscovery(String discovery) { + this.discovery = discovery; + } + + public String getDiscovery() { + return discovery; + } + + public String getMetadatafield() { + return metadatafield; + } + + public void setMetadatafield(String metadatafield) { + this.metadatafield = metadatafield; + } + + public void setDisableAuthSource(boolean disableAuthSource) { + this.disableAuthSource = disableAuthSource; + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/edit/service/AssociateItemModeService.java b/dspace-api/src/main/java/org/dspace/content/edit/service/AssociateItemModeService.java new file mode 100644 index 000000000000..9193c3f46f3a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/edit/service/AssociateItemModeService.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.content.edit.service; + +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; + +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.edit.AssociateItemMode; +import org.dspace.core.Context; + +/** + * @author Danilo Di Nuzzo (danilo.dinuzzo at 4science.it) + * @author Florian Gantner (florian.gantner@uni-bamberg.de) + * + */ +public interface AssociateItemModeService { + + static final String ASSOCIATEITEMMODECONF_PREFIX = "associatitem.mode."; + + /** + * Finds all edit mode for the given item filtered by logged user privileges + * @param context DSpace context + * @param item + * @return + */ + List findModes(Context context, Item item) throws SQLException; + + /** + * Finds all edit mode for the given item filtered by logged user privileges + * @param context DSpace context + * @param itemId id of item + * @return + */ + List findModes(Context context, UUID itemId) throws SQLException; + + /** + * Finds an edit mode by item and edit name, returns null if not exists + * @param context DSpace context + * @param item UUID Item + * @param name edit mode name + * @return + * @throws SQLException + */ + AssociateItemMode findMode(Context context, Item item, String name) throws SQLException; + + /** + * Check if the current user can edit the given item, based on the all the + * configured edit modes (verifying if there is at least one edit mode enabled + * for him). + * + * @param context the DSpace context + * @param item the item + * @return true if the given item is editable, false otherwise + */ + boolean canEdit(Context context, Item item); + + public List findModes(Context context, Item item, boolean checkSecurity) + throws SQLException, AuthorizeException; +} diff --git a/dspace-api/src/main/java/org/dspace/content/edit/service/AssociateItemService.java b/dspace-api/src/main/java/org/dspace/content/edit/service/AssociateItemService.java new file mode 100644 index 000000000000..f3b004bb8a2a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/edit/service/AssociateItemService.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.edit.service; + +import java.sql.SQLException; +import java.util.UUID; + +import org.dspace.app.exception.ResourceAlreadyExistsException; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; + +/** + * Service interface class for the AssociateItem object. + * The implementation of this class is responsible for all + * business logic calls for the AssociateItem object and is autowired + * by spring + * + * @author Danilo Di Nuzzo (danilo.dinuzzo at 4science.it) + * @author Florian Gantner (florian.gantner@uni-bamberg.de) + * + * + */ +public interface AssociateItemService { + + ItemService getItemService(); + + /** + * Create some metadata + * @param context + * @param sourceID the item where the metadata is created + * @param targetID the item where the metadata points to + * @param mode specified mode with the security config and the metadatafield + * @return + * @throws SQLException + * @throws AuthorizeException + * @throws ResourceAlreadyExistsException when some metadata already exists + * @throws IllegalArgumentException + */ + boolean create(Context context, UUID sourceID, UUID targetID, String mode) throws SQLException, + AuthorizeException, ResourceAlreadyExistsException, IllegalArgumentException; + + /** + * Delete any metadata fields with authority between the item with sourceID and targetID + * @param context + * @param sourceID item containing the metadata + * @param targetID item where the metadata points to + * @param mode specified mode with the security config and the metadatafield + * @return + * @throws SQLException + * @throws AuthorizeException + * @throws IllegalArgumentException + */ + boolean delete(Context context, UUID sourceID, UUID targetID, String mode) throws SQLException, + AuthorizeException, IllegalArgumentException; +} diff --git a/dspace-api/src/main/java/org/dspace/content/edit/service/impl/AssociateItemModeServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/edit/service/impl/AssociateItemModeServiceImpl.java new file mode 100644 index 000000000000..e6bfe7584a05 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/edit/service/impl/AssociateItemModeServiceImpl.java @@ -0,0 +1,136 @@ +/** + * 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.edit.service.impl; + +import static org.apache.commons.lang3.StringUtils.isBlank; + +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.dspace.content.Item; +import org.dspace.content.edit.AssociateItemMode; +import org.dspace.content.edit.service.AssociateItemModeService; +import org.dspace.content.logic.LogicalStatementException; +import org.dspace.content.security.service.CrisSecurityService; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.dspace.core.exception.SQLRuntimeException; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Service implementation for the AssociateItemMode object. + * This class is responsible for all business logic calls + * for the Item object and is autowired by spring. + * + * @author Danilo Di Nuzzo (danilo.dinuzzo at 4science.it) + * @author Florian gantner (florian.gantner@uni-bamberg.de) + */ +public class AssociateItemModeServiceImpl implements AssociateItemModeService { + + @Autowired + private ItemService itemService; + @Autowired + private CrisSecurityService crisSecurityService; + + private Map> associateModesMap; + + @Override + public List findModes(Context context, Item item) throws SQLException { + return findModes(context, item, true); + } + + public List findModes(Context context, Item item, boolean checkSecurity) throws SQLException { + + if (context.getCurrentUser() == null) { + return List.of(); + } + + List configuredModes = findAssociateItemModesByItem(item); + + if (!checkSecurity) { + return configuredModes; + } + + return configuredModes.stream() + .filter(associateMode -> hasAccess(context, item, associateMode)) + .collect(Collectors.toList()); + } + + @Override + public List findModes(Context context, UUID itemId) throws SQLException { + return findModes(context, itemService.find(context, itemId)); + } + + @Override + public AssociateItemMode findMode(Context context, Item item, String name) throws SQLException { + List modes = findModes(context, item, false); + return modes.stream() + .filter(mode -> mode.getName().equalsIgnoreCase(name)) + .findFirst() + .orElse(null); + } + + @Override + public boolean canEdit(Context context, Item item) { + + if (context.getCurrentUser() == null) { + return false; + } + + return findAssociateItemModesByItem(item).stream() + .anyMatch(associateMode -> hasAccess(context, item, associateMode)); + + } + + private boolean hasAccess(Context context, Item item, AssociateItemMode accessitemmode) { + try { + // Check access condition on target if some condition on the item is fulfilled. + if (Objects.nonNull(accessitemmode.getConditionTarget())) { + try { + return crisSecurityService.hasAccess(context, item, context.getCurrentUser(), accessitemmode) && + accessitemmode.getConditionTarget().getResult(context, item); + } catch (LogicalStatementException s) { + // do nothing + throw new SQLException(); + } + } else { + return crisSecurityService.hasAccess(context, item, context.getCurrentUser(), accessitemmode); + } + } catch (SQLException e) { + throw new SQLRuntimeException(e); + } + } + + private List findAssociateItemModesByItem(Item item) { + + List defaultModes = List.of(); + + if (item == null) { + return defaultModes; + } + + String entityType = itemService.getEntityTypeLabel(item); + if (isBlank(entityType)) { + return defaultModes; + } + + return getAssociateModesMap().getOrDefault(entityType.toLowerCase(), defaultModes); + } + + public Map> getAssociateModesMap() { + return associateModesMap; + } + + public void setAssociateModesMap(Map> associateModesMap) { + this.associateModesMap = associateModesMap; + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/edit/service/impl/AssociateItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/edit/service/impl/AssociateItemServiceImpl.java new file mode 100644 index 000000000000..fb73a11b0f87 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/edit/service/impl/AssociateItemServiceImpl.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.content.edit.service.impl; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +import org.dspace.app.exception.ResourceAlreadyExistsException; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Item; +import org.dspace.content.MetadataField; +import org.dspace.content.MetadataValue; +import org.dspace.content.dao.ItemDAO; +import org.dspace.content.edit.AssociateItem; +import org.dspace.content.edit.AssociateItemMode; +import org.dspace.content.edit.service.AssociateItemModeService; +import org.dspace.content.edit.service.AssociateItemService; +import org.dspace.content.security.service.CrisSecurityService; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.MetadataFieldService; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Service implementation for the AssociateItem object. + * This class is responsible for all business logic calls + * for the Item object and is autowired by spring. + * Mainly creating and removing connections (metadata with authority) between some item sourceID + * and some target targetID as the authority value. The metadata field is being readed from the {@AssociateItemMode} + * configuration. + * + * @author Danilo Di Nuzzo (danilo.dinuzzo at 4science.it) + * @author Florian Gantner (florian.gantner@uni-bamberg.de) + * + */ +public class AssociateItemServiceImpl implements AssociateItemService { + + @Autowired(required = true) + private ItemService itemService; + + @Autowired(required = true) + private ItemDAO itemDAO; + + @Autowired(required = true) + private AssociateItemModeService modeService; + + @Autowired(required = true) + private MetadataFieldService metadatafieldService; + + @Autowired(required = true) + private CrisSecurityService crisSecurityService; + + /* (non-Javadoc) + * @see org.dspace.content.edit.service.EditItemService#getItemService() + */ + @Override + public ItemService getItemService() { + return itemService; + } + + private AssociateItem find(Context context, UUID sourceID, UUID targetID, String mode) + throws SQLException, AuthorizeException { + boolean hasAccess = false; + Item targetItem = itemService.find(context, targetID); + Item sourceItem = itemService.find(context, sourceID); + + AssociateItemMode associateItemMode = null; + EPerson currentUser = context.getCurrentUser(); + //TODO:assume nonNull for accessmode and items + if (currentUser == null) { + throw new AuthorizeException(); + } else { + + associateItemMode = modeService.findMode(context, targetItem, mode); + if (associateItemMode == null) { + return null; + } + /* + + if (AssociateItemMode.NONE.equals(mode)) { + return AssociateItemMode.none(context, target); + }*/ + } + return new AssociateItem(context, sourceItem, targetItem, associateItemMode); + } + + private boolean checkConditions(Context context, AssociateItem associateItem) + throws SQLException { + + if (associateItem.getMode().isdisableAuthSource()) { + context.turnOffAuthorisationSystem(); + } + //Check conditions on source and target items + if (Objects.nonNull(associateItem.getMode().getConditionSource())) { + if (associateItem.getMode().getConditionSource().getResult(context, associateItem.getSourceitem()) + == false) { + return false; + } + } + if (Objects.nonNull(associateItem.getMode().getItemTypeSource())) { + if (!itemService.getEntityType(associateItem.getSourceitem()) + .contentEquals(associateItem.getMode().getItemTypeSource())) { + return false; + } + } + if (Objects.nonNull(associateItem.getMode().getItemTypeTarget())) { + if (!itemService.getEntityType(associateItem.getTargetitem()) + .contentEquals(associateItem.getMode().getItemTypeTarget())) { + return false; + } + } + if (Objects.nonNull(associateItem.getMode().getConditionTarget())) { + if (associateItem.getMode().getConditionTarget().getResult(context, associateItem.getTargetitem())) { + return false; + } + } + return crisSecurityService + .hasAccess(context, associateItem.getTargetitem(), context.getCurrentUser(), associateItem.getMode()); + } + + @Override + public boolean create(Context context, UUID sourceID, UUID targetID, String mode) + throws SQLException, AuthorizeException, ResourceAlreadyExistsException, IllegalArgumentException { + AssociateItem associateItem = this.find(context, sourceID, targetID, mode); + if (associateItem == null) { + throw new IllegalArgumentException("associateitem does not exist"); + } + boolean hasAccess = checkConditions(context, associateItem); + if (!hasAccess) { + throw new AuthorizeException(); + } + //cases: metadata already exist + List mdvs = itemService.getMetadataByMetadataString(associateItem.getSourceitem(), + associateItem.getMode().getMetadatafield()); + // check existing value, if some pointer to target exist + if (Objects.nonNull(mdvs) && !mdvs.isEmpty() + && mdvs.stream().anyMatch(mdv -> Objects.nonNull(mdv.getAuthority()) + && mdv.getAuthority().contentEquals(associateItem.getTargetitem().getID().toString()))) { + // Value exists + throw new ResourceAlreadyExistsException("metadata association already exist"); + } + + MetadataField mf = + this.metadatafieldService.findByString(context, associateItem.getMode().getMetadatafield(), '.'); + if (mf == null) { + throw new IllegalArgumentException("unknown Metadatafield"); + } + + itemService.addMetadata(context, associateItem.getSourceitem(), mf, null, + associateItem.getTargetitem().getName(), associateItem.getTargetitem().getID().toString(), 600); + itemService.update(context, associateItem.getSourceitem()); + if (context.ignoreAuthorization()) { + context.restoreAuthSystemState(); + } + return true; + } + + @Override + public boolean delete(Context context, UUID sourceID, UUID targetID, String mode) + throws SQLException, AuthorizeException, IllegalArgumentException { + AssociateItem associateItem = this.find(context, sourceID, targetID, mode); + if (associateItem == null) { + throw new IllegalArgumentException("specified AssociateItem does not exist"); + } + boolean hasAccess = checkConditions(context, associateItem); + if (!hasAccess) { + throw new AuthorizeException(); + } + List mdvs = itemService.getMetadataByMetadataString(associateItem.getSourceitem(), + associateItem.getMode().getMetadatafield()); + if (Objects.isNull(mdvs) || mdvs.isEmpty()) { + throw new IllegalArgumentException("metadatavalue not existed or already deleted"); + } + List toremove = new ArrayList<>(); + for (MetadataValue mdv : mdvs) { + if (mdv.getAuthority().contentEquals(associateItem.getTargetitem().getID().toString())) { + toremove.add(mdv); + } + } + if (toremove.isEmpty()) { + throw new IllegalArgumentException( + "no metadatavalue to targetitem found. perhaps metadatavalue not existed or deleted", null); + } + itemService.removeMetadataValues(context, associateItem.getSourceitem(), toremove); + itemService.update(context, associateItem.getSourceitem()); + if (context.ignoreAuthorization()) { + context.restoreAuthSystemState(); + } + return true; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/AssociateItemRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/AssociateItemRestController.java new file mode 100644 index 000000000000..03cffc44555d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/AssociateItemRestController.java @@ -0,0 +1,129 @@ +/** + * 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.Arrays; +import java.util.Objects; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.dspace.app.exception.ResourceAlreadyExistsException; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.model.AssociateItemModeRest; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.edit.service.AssociateItemService; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.hateoas.Link; +import org.springframework.security.access.prepost.PreAuthorize; +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 is a rest controller to change associateitem modes. + * It provided basic functionality and just returns the response code. + * Rest Endpoints needs authentication. + * @author Florian Gantner (florian.gantner@uni-bamberg.de) + */ +@RequestMapping("/api/" + AssociateItemModeRest.CATEGORY + "/" + AssociateItemModeRest.NAMESHORT) +@RestController +public class AssociateItemRestController implements InitializingBean { + + @Autowired + DiscoverableEndpointsService discoverableEndpointsService; + + @Autowired + private ConfigurationService configurationService; + + @Autowired + private AssociateItemService associateItemService; + + @Autowired + ConverterService converter; + + @Autowired + Utils utils; + + @Override + public void afterPropertiesSet() { + discoverableEndpointsService + .register(this, Arrays.asList( + Link.of("/api/" + AssociateItemModeRest.CATEGORY + "/" + AssociateItemModeRest.NAMESHORT, + AssociateItemModeRest.NAMESHORT))); + } + + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @RequestMapping(method = RequestMethod.PUT, value = "/create") + public boolean create(HttpServletRequest request, + HttpServletResponse response, @RequestParam(name = "sourceuuid") UUID sourceuuid, + @RequestParam(name = "targetuuid") UUID targetuuid, String modename) { + Context context = null; + boolean created = false; + try { + if (configurationService.getBooleanProperty("associateitem.enabled", true) == false) { + response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE); + return false; + } + context = ContextUtil.obtainContext(request); + created = this.associateItemService.create(context, sourceuuid, targetuuid, modename); + response.setStatus(HttpServletResponse.SC_CREATED); + context.commit(); + } catch (ResourceAlreadyExistsException e) { + response.setStatus(HttpServletResponse.SC_OK); + } catch (AuthorizeException e) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + } catch (SQLException e) { + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } catch (Exception e) { + //TODO: check other cases + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } finally { + if (Objects.nonNull(context) && context.isValid()) { + context.close(); + } + } + return created; + } + + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @RequestMapping(method = RequestMethod.DELETE, value = "/delete") + public boolean delete(HttpServletRequest request, + HttpServletResponse response, @RequestParam(name = "sourceuuid") UUID sourceuuid, + @RequestParam(name = "targetuuid") UUID targetuuid, String modename) { + Context context = null; + boolean delete = false; + try { + if (configurationService.getBooleanProperty("associateitem.enabled", true) == false) { + response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE); + return false; + } + context = ContextUtil.obtainContext(request); + delete = this.associateItemService.delete(context, sourceuuid, targetuuid, modename); + context.commit(); + } catch (AuthorizeException e) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + } catch (SQLException e) { + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } catch (Exception e) { + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } finally { + if (Objects.nonNull(context) && context.isValid()) { + context.close(); + } + } + return delete; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AssociateItemModeConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AssociateItemModeConverter.java new file mode 100644 index 000000000000..2c6c2a5cefa2 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AssociateItemModeConverter.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.AssociateItemModeRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.edit.AssociateItemMode; +import org.springframework.stereotype.Component; + +/** + * @author Danilo Di Nuzzo (danilo.dinuzzo at 4science.it) + * @author Florian Gantner (florian.gantner@uni-bamberg.de) + * + */ +@Component +public class AssociateItemModeConverter implements DSpaceConverter { + + /* (non-Javadoc) + * @see org.dspace.app.rest.converter.DSpaceConverter# + * convert(java.lang.Object, org.dspace.app.rest.projection.Projection) + */ + @Override + public AssociateItemModeRest convert(AssociateItemMode model, Projection projection) { + AssociateItemModeRest rest = new AssociateItemModeRest(); + rest.setId(model.getName()); + rest.setName(model.getName()); + rest.setLabel(model.getLabel()); + rest.setMetadatafield(model.getMetadatafield()); + rest.setDiscovery(model.getDiscovery()); + return rest; + } + + /* (non-Javadoc) + * @see org.dspace.app.rest.converter.DSpaceConverter#getModelClass() + */ + @Override + public Class getModelClass() { + return AssociateItemMode.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AssociateItemModeRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AssociateItemModeRest.java new file mode 100644 index 000000000000..b7063152c6a7 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AssociateItemModeRest.java @@ -0,0 +1,95 @@ +/** + * 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; + +/** + * The AssociateItemMode REST Resource + * + * @author Danilo Di Nuzzo (danilo.dinuzzo at 4science.it) + * @author Florian Gantner (florian.gantner@uni-bamberg.de) + */ +public class AssociateItemModeRest extends BaseObjectRest { + + private static final long serialVersionUID = -3615146164199721822L; + public static final String NAME = "associateitemmode"; + public static final String NAMESHORT = "associateitem"; + public static final String CATEGORY = RestAddressableModel.CORE; + + private String name; + private String label; + private Integer security; + private String discovery; + private String metadatafield; + + /* (non-Javadoc) + * @see org.dspace.app.rest.model.RestModel#getType() + */ + @Override + public String getType() { + return NAME; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public Integer getSecurity() { + return security; + } + + public void setSecurity(Integer security) { + this.security = security; + } + + public String getMetadatafield() { + return metadatafield; + } + + public void setMetadatafield(String metadatafield) { + this.metadatafield = metadatafield; + } + + public String getDiscovery() { + return discovery; + } + + public void setDiscovery(String discovery) { + this.discovery = discovery; + } + + /* (non-Javadoc) + * @see org.dspace.app.rest.model.RestAddressableModel#getCategory() + */ + @Override + public String getCategory() { + return CATEGORY; + } + + /* (non-Javadoc) + * @see org.dspace.app.rest.model.RestAddressableModel#getController() + */ + @Override + public Class getController() { + return RestResourceController.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AssociateItemModeResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AssociateItemModeResource.java new file mode 100644 index 000000000000..b04a72a19b2c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AssociateItemModeResource.java @@ -0,0 +1,28 @@ +/** + * 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.AssociateItemModeRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * EditItemMode Rest HAL Resource. The HAL Resource wraps the REST Resource + * adding support for the links and embedded resources + * + * @author Danilo Di Nuzzo (danilo.dinuzzo at 4science.it) + * @author Florian Gantner (florian.gantner@uni-bamberg.de) + */ +@RelNameDSpaceResource(AssociateItemModeRest.NAME) +public class AssociateItemModeResource extends DSpaceResource { + + public AssociateItemModeResource(AssociateItemModeRest witem, Utils utils) { + super(witem, utils); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AssociateItemModeRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AssociateItemModeRestRepository.java new file mode 100644 index 000000000000..8672bb03989c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AssociateItemModeRestRepository.java @@ -0,0 +1,113 @@ +/** + * 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 java.util.UUID; + +import org.dspace.app.rest.Parameter; +import org.dspace.app.rest.SearchRestMethod; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.model.AssociateItemModeRest; +import org.dspace.content.Item; +import org.dspace.content.edit.AssociateItemMode; +import org.dspace.content.edit.service.AssociateItemModeService; +import org.dspace.content.service.ItemService; +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.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * @author Danilo Di Nuzzo (danilo.dinuzzo at 4science.it) + * @author Florian Gantner (florian.gantner@uni-bamberg.de) + * + */ +@Component(AssociateItemModeRest.CATEGORY + "." + AssociateItemModeRest.NAME) +public class AssociateItemModeRestRepository + extends DSpaceRestRepository { + + @Autowired + private AssociateItemModeService aimService; + + @Autowired + ItemService itemService; + + /* (non-Javadoc) + * @see org.dspace.app.rest.repository.DSpaceRestRepository#findOne(org.dspace.core.Context, java.io.Serializable) + */ + @Override + @PreAuthorize("permitAll") + public AssociateItemModeRest findOne(Context context, String data) { + AssociateItemMode mode = null; + String uuid = null; + String modeName = null; + String[] values = data.split(":"); + if (values != null && values.length == 2) { + uuid = values[0]; + modeName = values[1]; + } else { + throw new DSpaceBadRequestException( + "Given parameters are incomplete. Expected :, Received: " + data); + } + try { + UUID itemUuid = UUID.fromString(uuid); + Item item = itemService.find(context, itemUuid); + if (item == null) { + throw new ResourceNotFoundException("No such item with uuid : " + itemUuid); + } + mode = aimService.findMode(context, item, modeName); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + if (mode == null) { + return null; + } + return converter.toRest(mode, utils.obtainProjection()); + } + + /* (non-Javadoc) + * @see org.dspace.app.rest.repository.DSpaceRestRepository# + * findAll(org.dspace.core.Context, org.springframework.data.domain.Pageable) + */ + @Override + public Page findAll(Context context, Pageable pageable) { + throw new RepositoryMethodNotImplementedException("No implementation found; Method not Implemented!", ""); + } + + /* (non-Javadoc) + * @see org.dspace.app.rest.repository.DSpaceRestRepository#getDomainClass() + */ + @Override + public Class getDomainClass() { + return AssociateItemModeRest.class; + } + + @PreAuthorize("permitAll") + @SearchRestMethod(name = "findModesById") + public Page findModesById(@Parameter(value = "uuid", required = true) UUID id, + Pageable pageable) { + Context context = obtainContext(); + List modes = null; + try { + modes = aimService.findModes(context, id); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + if (modes == null) { + return null; + } + return converter.toRestPage(modes, pageable, modes.size(), utils.obtainProjection()); + } + +} diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 2e14eeec788c..2fed46eda460 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1755,6 +1755,7 @@ context-menu-entry.requestcorrection.enabled = true context-menu-entry.statistics.enabled = true context-menu-entry.subscriptions.enabled = true context-menu-entry.itemversion.enabled = true +context-menu-entry.associateitem.enabled = true ################################################ ################ CrossWalk configurations ########### @@ -1797,6 +1798,13 @@ inputforms.custom.step-type = dbms-import.replace.metadata-to-keep = dspace.entity.type + +#------------------------------------------------------------------# +#----------------Associate Item CONFIGURATION----------------------# +#------------------------------------------------------------------# +# Option to disable associate item functionality +#associateitem.enabled = false + #------------------------------------------------------------------# #------------------SUBMISSION CONFIGURATION------------------------# #------------------------------------------------------------------# diff --git a/dspace/config/modules/rest.cfg b/dspace/config/modules/rest.cfg index faf7b248046b..eb096472fe24 100644 --- a/dspace/config/modules/rest.cfg +++ b/dspace/config/modules/rest.cfg @@ -63,6 +63,7 @@ rest.properties.exposed = context-menu-entry.claim.enabled rest.properties.exposed = context-menu-entry.editdso.enabled rest.properties.exposed = context-menu-entry.editrelationships.enabled rest.properties.exposed = context-menu-entry.editsubmission.enabled +rest.properties.exposed = context-menu-entry.associateitem.enabled rest.properties.exposed = context-menu-entry.exportcollection.enabled rest.properties.exposed = context-menu-entry.exportitem.enabled rest.properties.exposed = context-menu-entry.requestcorrection.enabled diff --git a/dspace/config/spring/api/associateitem-service.xml b/dspace/config/spring/api/associateitem-service.xml new file mode 100644 index 000000000000..cd182c57f001 --- /dev/null +++ b/dspace/config/spring/api/associateitem-service.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + CUSTOM + + + + + crispj.investigator + + + + + + + + + + + + + + + + + + + + + + +