diff --git a/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java index 1e4f4daa07..5c1ccb96f8 100644 --- a/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java @@ -268,6 +268,30 @@ public class BundleServiceImpl extends DSpaceObjectServiceImpl implement return authorizeService.getPolicies(context, bundle); } + @Override + public void moveBitstream(Context context, Bundle bundle, int from, int to) + throws AuthorizeException, SQLException { + List bitstreams = bundle.getBitstreams(); + if (bitstreams.size() < 1 || from >= bitstreams.size() || to >= bitstreams.size() || from < 0 || to < 0) { + throw new IllegalArgumentException( + "Invalid 'from' and 'to' arguments supplied for moving a bitstream within bundle " + + bundle.getID() + ". from: " + from + "; to: " + to + ); + } + List bitstreamIds = new LinkedList<>(); + for (Bitstream bitstream : bitstreams) { + bitstreamIds.add(bitstream.getID()); + } + if (from < to) { + bitstreamIds.add(to + 1, bitstreamIds.get(from)); + bitstreamIds.remove(from); + } else { + bitstreamIds.add(to, bitstreamIds.get(from)); + bitstreamIds.remove(from + 1); + } + setOrder(context, bundle, bitstreamIds.toArray(new UUID[bitstreamIds.size()])); + } + @Override public void setOrder(Context context, Bundle bundle, UUID[] bitstreamIds) throws AuthorizeException, SQLException { authorizeService.authorizeAction(context, bundle, Constants.WRITE); diff --git a/dspace-api/src/main/java/org/dspace/content/service/BundleService.java b/dspace-api/src/main/java/org/dspace/content/service/BundleService.java index e7fc0ac8f1..38971f08ea 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/BundleService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/BundleService.java @@ -109,6 +109,18 @@ public interface BundleService extends DSpaceObjectService, DSpaceObject public List getBundlePolicies(Context context, Bundle bundle) throws SQLException; + /** + * Moves a bitstream within a bundle from one place to another, shifting all other bitstreams in the process + * + * @param context DSpace Context + * @param bundle The bitstream bundle + * @param from The index of the bitstream to move + * @param to The index to move the bitstream to + * @throws AuthorizeException when an SQL error has occurred (querying DSpace) + * @throws SQLException If the user can't make the changes + */ + public void moveBitstream(Context context, Bundle bundle, int from, int to) throws AuthorizeException, SQLException; + /** * Changes bitstream order according to the array * diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleRestRepository.java index 0955798f90..2c53f593bb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleRestRepository.java @@ -10,11 +10,16 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; import java.util.UUID; +import javax.servlet.http.HttpServletRequest; + import org.dspace.app.rest.converter.BundleConverter; import org.dspace.app.rest.model.BundleRest; import org.dspace.app.rest.model.hateoas.BundleResource; import org.dspace.app.rest.model.hateoas.DSpaceResource; +import org.dspace.app.rest.model.patch.Patch; +import org.dspace.app.rest.repository.patch.BundlePatch; import org.dspace.app.rest.repository.patch.DSpaceObjectPatch; +import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bundle; import org.dspace.content.service.BundleService; import org.dspace.core.Context; @@ -36,9 +41,13 @@ public class BundleRestRepository extends DSpaceObjectRestRepository() {}); + BundleConverter dsoConverter, + BundlePatch dsoPatch) { + super(dsoService, dsoConverter, dsoPatch); this.bundleService = dsoService; } @@ -60,6 +69,13 @@ public class BundleRestRepository extends DSpaceObjectRestRepository getDomainClass() { return BundleRest.class; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/BundlePatch.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/BundlePatch.java new file mode 100644 index 0000000000..103ab38cfc --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/BundlePatch.java @@ -0,0 +1,23 @@ +package org.dspace.app.rest.repository.patch; + +import org.dspace.app.rest.model.BundleRest; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.repository.patch.factories.BundleOperationFactory; +import org.dspace.app.rest.repository.patch.factories.impl.ResourcePatchOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Created by kristof on 24/09/2019 + */ +@Component +public class BundlePatch extends DSpaceObjectPatch { + + @Autowired + BundleOperationFactory patchFactory; + + protected BundleRest move(BundleRest restModel, Operation operation) { + ResourcePatchOperation patchOperation = patchFactory.getMoveOperation(); + return patchOperation.perform(restModel, operation); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/factories/BundleOperationFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/factories/BundleOperationFactory.java new file mode 100644 index 0000000000..1dc79bf4a5 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/factories/BundleOperationFactory.java @@ -0,0 +1,21 @@ +package org.dspace.app.rest.repository.patch.factories; + +import org.dspace.app.rest.model.BundleRest; +import org.dspace.app.rest.repository.patch.factories.impl.BundleMoveOperation; +import org.dspace.app.rest.repository.patch.factories.impl.ResourcePatchOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Created by kristof on 24/09/2019 + */ +@Component +public class BundleOperationFactory { + + @Autowired + BundleMoveOperation bundleMoveOperation; + + public ResourcePatchOperation getMoveOperation() { + return bundleMoveOperation; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/BundleMoveOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/BundleMoveOperation.java new file mode 100644 index 0000000000..4246bdb5ae --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/BundleMoveOperation.java @@ -0,0 +1,68 @@ +package org.dspace.app.rest.repository.patch.factories.impl; + +import java.sql.SQLException; + +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.model.BundleRest; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Bundle; +import org.dspace.content.service.BundleService; +import org.dspace.core.Context; +import org.dspace.services.RequestService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Created by kristof on 24/09/2019 + */ +@Component +public class BundleMoveOperation extends MovePatchOperation { + + @Autowired + BundleService bundleService; + + @Autowired + RequestService requestService; + + @Override + public BundleRest move(BundleRest resource, Operation operation) { + Context context = ContextUtil.obtainContext(requestService.getCurrentRequest().getServletRequest()); + try { + Bundle bundle = bundleService.findByIdOrLegacyId(context, resource.getId()); + int totalAmount = bundle.getBitstreams().size(); + + if(totalAmount < 1) { + throw new DSpaceBadRequestException(createMoveExceptionMessage(bundle, from, to, "No sub-communities found.")); + } + if(from >= totalAmount) { + throw new DSpaceBadRequestException(createMoveExceptionMessage(bundle, from, to, "\"from\" location out of bounds. Latest available position: " + (totalAmount-1))); + } + if(to >= totalAmount) { + throw new DSpaceBadRequestException(createMoveExceptionMessage(bundle, from, to, "\"to\" location out of bounds. Latest available position: " + (totalAmount-1))); + } + + bundleService.moveBitstream(context, bundle, from, to); + } catch (SQLException | AuthorizeException e) { + throw new DSpaceBadRequestException(e.getMessage(), e); + } + + return resource; + } + + @Override + protected Class getArrayClassForEvaluation() { + return Integer[].class; + } + + @Override + protected Class getClassForEvaluation() { + return Integer.class; + } + + private String createMoveExceptionMessage(Bundle bundle, int from, int to, String message) { + return "Failed moving bitstreams of bundle with id " + bundle.getID() + " from location " + from + " to " + to + ": " + message; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/MovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/MovePatchOperation.java new file mode 100644 index 0000000000..968a3c3403 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/MovePatchOperation.java @@ -0,0 +1,57 @@ +package org.dspace.app.rest.repository.patch.factories.impl; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.model.RestModel; +import org.dspace.app.rest.model.patch.MoveOperation; +import org.dspace.app.rest.model.patch.Operation; + +/** + * Created by kristof on 24/09/2019 + */ +public abstract class MovePatchOperation extends PatchOperation { + + protected int from; + protected int to; + + @Override + public R perform(R resource, Operation operation) { + checkMoveOperation(operation); + return move(resource, operation); + } + + abstract R move(R resource, Operation operation); + + private void checkMoveOperation(Operation operation) { + if(!(operation instanceof MoveOperation)) { + throw new DSpaceBadRequestException("Expected a MoveOperation, but received " + operation.getClass().getName() + " instead."); + } + from = getLocationFromPath(((MoveOperation)operation).getFrom()); + to = getLocationFromPath(operation.getPath()); + if(from == to) { + throw new DSpaceBadRequestException("The \"from\" location must be different from the \"to\" location."); + } + if(from < 0) { + throw new DSpaceBadRequestException("A negative \"from\" location was provided: " + from); + } + if(to < 0) { + throw new DSpaceBadRequestException("A negative \"to\" location was provided: " + to); + } + } + + protected int getLocationFromPath(String path) { + String[] parts = StringUtils.split(path, "/"); + String locationStr; + if(parts.length > 1) { + if(StringUtils.equals(parts[parts.length-1], "href")) { + locationStr = parts[parts.length-2]; + } else { + locationStr = parts[parts.length-1]; + } + } else { + locationStr = parts[0]; + } + return Integer.parseInt(locationStr); + } +} +