diff --git a/Dockerfile.jdk8 b/Dockerfile.jdk8 index 9c3793568a..aa486d47be 100644 --- a/Dockerfile.jdk8 +++ b/Dockerfile.jdk8 @@ -54,7 +54,13 @@ EXPOSE 8080 8009 ENV JAVA_OPTS=-Xmx2000m -RUN mv /usr/local/tomcat/webapps/ROOT /usr/local/tomcat/webapps/ROOT.bk && \ - ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/ROOT && \ - ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/server && \ +# Run the "server" webapp off the /server path (e.g. http://localhost:8080/server/) +# and the v6.x (deprecated) REST API off the "/rest" path +RUN ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/server && \ ln -s $DSPACE_INSTALL/webapps/rest /usr/local/tomcat/webapps/rest +# If you wish to run "server" webapp off the ROOT path, then comment out the above RUN, and uncomment the below RUN. +# You also MUST update the URL in dspace/src/main/docker/local.cfg +# Please note that server webapp should only run on one path at a time. +#RUN mv /usr/local/tomcat/webapps/ROOT /usr/local/tomcat/webapps/ROOT.bk && \ +# ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/ROOT && \ +# ln -s $DSPACE_INSTALL/webapps/rest /usr/local/tomcat/webapps/rest \ No newline at end of file diff --git a/Dockerfile.jdk8-test b/Dockerfile.jdk8-test index 011045adba..6bbd43c3ea 100644 --- a/Dockerfile.jdk8-test +++ b/Dockerfile.jdk8-test @@ -54,11 +54,17 @@ EXPOSE 8080 8009 ENV JAVA_OPTS=-Xmx2000m -RUN mv /usr/local/tomcat/webapps/ROOT /usr/local/tomcat/webapps/ROOT.bk && \ - ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/ROOT && \ - ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/server && \ +# Run the "server" webapp off the /server path (e.g. http://localhost:8080/server/) +# and the v6.x (deprecated) REST API off the "/rest" path +RUN ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/server && \ ln -s $DSPACE_INSTALL/webapps/rest /usr/local/tomcat/webapps/rest +# If you wish to run "server" webapp off the ROOT path, then comment out the above RUN, and uncomment the below RUN. +# You also MUST update the URL in dspace/src/main/docker/local.cfg +# Please note that server webapp should only run on one path at a time. +#RUN mv /usr/local/tomcat/webapps/ROOT /usr/local/tomcat/webapps/ROOT.bk && \ +# ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/ROOT && \ +# ln -s $DSPACE_INSTALL/webapps/rest /usr/local/tomcat/webapps/rest +# Overwrite the v6.x (deprecated) REST API's web.xml, so that we can run it on HTTP (defaults to requiring HTTPS) 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/rest/WEB-INF/web.xml +RUN sed -i -e "s|\${dspace.dir}|$DSPACE_INSTALL|" $DSPACE_INSTALL/webapps/rest/WEB-INF/web.xml \ No newline at end of file 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 44498e6a18..e8fff71cf4 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 @@ -193,7 +193,7 @@ public class MetadataImport { * @param workflowNotify If the workflows should be used, whether to send notifications or not * @param useTemplate Use collection template if create new item * @return An array of BulkEditChange elements representing the items that have changed - * @throws MetadataImportException if something goes wrong + * @throws MetadataImportException if something goes wrong */ public List runImport(boolean change, boolean useWorkflow, @@ -402,13 +402,15 @@ public class MetadataImport { // Add the metadata to the item 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())) { + itemService.addMetadata(c, item, dcv.getSchema(), + dcv.getElement(), + dcv.getQualifier(), + dcv.getLanguage(), + dcv.getValue(), + dcv.getAuthority(), + dcv.getConfidence()); + } } //Add relations after all metadata has been processed for (BulkEditMetadataValue dcv : whatHasChanged.getAdds()) { @@ -778,8 +780,8 @@ public class MetadataImport { } // Create the relationship - int leftPlace = relationshipService.findLeftPlaceByLeftItem(c, leftItem) + 1; - int rightPlace = relationshipService.findRightPlaceByRightItem(c, rightItem) + 1; + int leftPlace = relationshipService.findNextLeftPlaceByLeftItem(c, leftItem); + int rightPlace = relationshipService.findNextRightPlaceByRightItem(c, rightItem); Relationship persistedRelationship = relationshipService.create(c, leftItem, rightItem, foundRelationshipType, leftPlace, rightPlace); relationshipService.update(c, persistedRelationship); diff --git a/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java b/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java index 435c28bcb9..ef6b0b538e 100644 --- a/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java +++ b/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java @@ -13,6 +13,12 @@ import java.lang.reflect.Method; import java.util.List; import java.util.TreeMap; +import org.apache.commons.cli.ParseException; +import org.apache.log4j.Logger; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.scripts.factory.ScriptServiceFactory; +import org.dspace.scripts.handler.DSpaceRunnableHandler; +import org.dspace.scripts.handler.impl.CommandLineDSpaceRunnableHandler; import org.dspace.servicemanager.DSpaceKernelImpl; import org.dspace.servicemanager.DSpaceKernelInit; import org.dspace.services.RequestService; @@ -27,6 +33,9 @@ import org.jdom.input.SAXBuilder; * @author Mark Diggory */ public class ScriptLauncher { + + private static final Logger log = Logger.getLogger(ScriptLauncher.class); + /** * The service manager kernel */ @@ -76,8 +85,9 @@ public class ScriptLauncher { } // Look up command in the configuration, and execute. - int status; - status = runOneCommand(commandConfigs, args); + + CommandLineDSpaceRunnableHandler commandLineDSpaceRunnableHandler = new CommandLineDSpaceRunnableHandler(); + int status = handleScript(args, commandConfigs, commandLineDSpaceRunnableHandler, kernelImpl); // Destroy the service kernel if it is still alive if (kernelImpl != null) { @@ -86,6 +96,50 @@ public class ScriptLauncher { } System.exit(status); + + } + + /** + * This method will take the arguments from a commandline input and it'll find the script that the first argument + * refers to and it'll execute this script. + * It can return a 1 or a 0 depending on whether the script failed or passed respectively + * @param args The arguments for the script and the script as first one in the array + * @param commandConfigs The Document + * @param dSpaceRunnableHandler The DSpaceRunnableHandler for this execution + * @param kernelImpl The relevant DSpaceKernelImpl + * @return A 1 or 0 depending on whether the script failed or passed respectively + */ + public static int handleScript(String[] args, Document commandConfigs, + DSpaceRunnableHandler dSpaceRunnableHandler, + DSpaceKernelImpl kernelImpl) { + int status; + DSpaceRunnable script = ScriptServiceFactory.getInstance().getScriptService().getScriptForName(args[0]); + if (script != null) { + status = executeScript(args, dSpaceRunnableHandler, script); + } else { + status = runOneCommand(commandConfigs, args, kernelImpl); + } + return status; + } + + /** + * This method will simply execute the script + * @param args The arguments of the script with the script name as first place in the array + * @param dSpaceRunnableHandler The relevant DSpaceRunnableHandler + * @param script The script to be executed + * @return A 1 or 0 depending on whether the script failed or passed respectively + */ + private static int executeScript(String[] args, DSpaceRunnableHandler dSpaceRunnableHandler, + DSpaceRunnable script) { + try { + script.initialize(args, dSpaceRunnableHandler); + script.run(); + return 0; + } catch (ParseException e) { + script.printHelp(); + e.printStackTrace(); + return 1; + } } protected static int runOneCommand(Document commandConfigs, String[] args) { @@ -98,7 +152,7 @@ public class ScriptLauncher { * @param commandConfigs Document * @param args the command line arguments given */ - public static int runOneCommand(Document commandConfigs, String[] args, DSpaceKernelImpl kernelImpl) { + protected static int runOneCommand(Document commandConfigs, String[] args, DSpaceKernelImpl kernelImpl) { String request = args[0]; Element root = commandConfigs.getRootElement(); List commands = root.getChildren("command"); 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 cbc3986dab..a6444a3890 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 @@ -89,6 +89,11 @@ public class DCInput { */ private boolean repeatable = false; + /** + * should name-variants be used? + */ + private boolean nameVariants = false; + /** * 'hint' text to display */ @@ -183,6 +188,9 @@ public class DCInput { String repStr = fieldMap.get("repeatable"); repeatable = "true".equalsIgnoreCase(repStr) || "yes".equalsIgnoreCase(repStr); + String nameVariantsString = fieldMap.get("name-variants"); + nameVariants = (StringUtils.isNotBlank(nameVariantsString)) ? + nameVariantsString.equalsIgnoreCase("true") : false; label = fieldMap.get("label"); inputType = fieldMap.get("input-type"); // these types are list-controlled @@ -269,6 +277,15 @@ public class DCInput { return isRepeatable(); } + /** + * Get the nameVariants flag for this row + * + * @return the nameVariants flag + */ + public boolean areNameVariantsAllowed() { + return nameVariants; + } + /** * Get the input type for this row * 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..66b6f819a8 100644 --- a/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/BundleServiceImpl.java @@ -7,6 +7,10 @@ */ package org.dspace.content; +import static org.dspace.core.Constants.ADD; +import static org.dspace.core.Constants.REMOVE; +import static org.dspace.core.Constants.WRITE; + import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; @@ -268,6 +272,81 @@ public class BundleServiceImpl extends DSpaceObjectServiceImpl implement return authorizeService.getPolicies(context, bundle); } + @Override + public void updateBitstreamOrder(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 moveBitstreamToBundle(Context context, Bundle targetBundle, Bitstream bitstream) + throws SQLException, AuthorizeException, IOException { + List bundles = new LinkedList<>(); + bundles.addAll(bitstream.getBundles()); + + if (hasSufficientMovePermissions(context, bundles, targetBundle)) { + this.addBitstream(context, targetBundle, bitstream); + this.update(context, targetBundle); + for (Bundle bundle : bundles) { + this.removeBitstream(context, bundle, bitstream); + this.update(context, bundle); + } + } + } + + + /** + * Verifies if the context (user) has sufficient rights to the bundles in order to move a bitstream + * + * @param context The context + * @param bundles The current bundles in which the bitstream resides + * @param targetBundle The target bundle + * @return true when the context has sufficient rights + * @throws AuthorizeException When one of the necessary rights is not present + */ + private boolean hasSufficientMovePermissions(final Context context, final List bundles, + final Bundle targetBundle) throws SQLException, AuthorizeException { + for (Bundle bundle : bundles) { + if (!authorizeService.authorizeActionBoolean(context, bundle, WRITE) || !authorizeService + .authorizeActionBoolean(context, bundle, REMOVE)) { + throw new AuthorizeException( + "The current user does not have WRITE and REMOVE access to the current bundle: " + bundle + .getID()); + } + } + if (!authorizeService.authorizeActionBoolean(context, targetBundle, WRITE) || !authorizeService + .authorizeActionBoolean(context, targetBundle, ADD)) { + throw new AuthorizeException( + "The current user does not have WRITE and ADD access to the target bundle: " + targetBundle + .getID()); + } + for (Item item : targetBundle.getItems()) { + if (!authorizeService.authorizeActionBoolean(context, item, WRITE)) { + throw new AuthorizeException( + "The current user does not have WRITE access to the target bundle's item: " + item.getID()); + } + } + return true; + } + @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/DSpaceObjectServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java index 7f3f97822a..ee188dc144 100644 --- a/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/DSpaceObjectServiceImpl.java @@ -146,7 +146,7 @@ public abstract class DSpaceObjectServiceImpl implements public List getMetadataByMetadataString(T dso, String mdString) { StringTokenizer dcf = new StringTokenizer(mdString, "."); - String[] tokens = { "", "", "" }; + String[] tokens = {"", "", ""}; int i = 0; while (dcf.hasMoreTokens()) { tokens[i] = dcf.nextToken().trim(); @@ -250,8 +250,11 @@ public abstract class DSpaceObjectServiceImpl implements } } 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()); + //Set place to list length of all metadatavalues for the given schema.element.qualifier combination. + // Subtract one to adhere to the 0 as first element rule + metadataValue.setPlace( + this.getMetadata(dso, metadataField.getMetadataSchema().getName(), metadataField.getElement(), + metadataField.getQualifier(), Item.ANY).size() - 1); metadataValue.setLanguage(lang == null ? null : lang.trim()); @@ -533,7 +536,7 @@ public abstract class DSpaceObjectServiceImpl implements protected String[] getMDValueByField(String field) { StringTokenizer dcf = new StringTokenizer(field, "."); - String[] tokens = { "", "", "" }; + String[] tokens = {"", "", ""}; int i = 0; while (dcf.hasMoreTokens()) { tokens[i] = dcf.nextToken().trim(); @@ -571,7 +574,7 @@ public abstract class DSpaceObjectServiceImpl implements if (compare == 0) { if (o1 instanceof RelationshipMetadataValue) { return 1; - } else if (o2 instanceof RelationshipMetadataValue) { + } else if (o2 instanceof RelationshipMetadataValue) { return -1; } } diff --git a/dspace-api/src/main/java/org/dspace/content/ProcessStatus.java b/dspace-api/src/main/java/org/dspace/content/ProcessStatus.java new file mode 100644 index 0000000000..ac4fb5f06c --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/ProcessStatus.java @@ -0,0 +1,19 @@ +/** + * 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 Enum holds a representation of all the possible states that a Process can be in + */ +public enum ProcessStatus { + SCHEDULED, + RUNNING, + COMPLETED, + FAILED + +} diff --git a/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java index ae00a4a5db..8012c29f6a 100644 --- a/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/RelationshipServiceImpl.java @@ -85,8 +85,12 @@ public class RelationshipServiceImpl implements RelationshipService { 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); + // This order of execution should be handled in the creation (create, updateplace, update relationship) + // for a proper place allocation + Relationship relationshipToReturn = relationshipDAO.create(context, relationship); + updatePlaceInRelationship(context, relationshipToReturn); + update(context, relationshipToReturn); + return relationshipToReturn; } else { throw new AuthorizeException( "You do not have write rights on this relationship's items"); @@ -98,18 +102,34 @@ public class RelationshipServiceImpl implements RelationshipService { } @Override - public void updatePlaceInRelationship(Context context, Relationship relationship, boolean isCreation) + public void updatePlaceInRelationship(Context context, Relationship relationship) throws SQLException, AuthorizeException { Item leftItem = relationship.getLeftItem(); + // Max value is used to ensure that these will get added to the back of the list and thus receive the highest + // (last) place as it's set to a -1 for creation + if (relationship.getLeftPlace() == -1) { + relationship.setLeftPlace(Integer.MAX_VALUE); + } + Item rightItem = relationship.getRightItem(); + if (relationship.getRightPlace() == -1) { + relationship.setRightPlace(Integer.MAX_VALUE); + } List leftRelationships = findByItemAndRelationshipType(context, leftItem, relationship.getRelationshipType(), true); - Item rightItem = relationship.getRightItem(); List rightRelationships = findByItemAndRelationshipType(context, rightItem, relationship.getRelationshipType(), false); + // These relationships are only deleted from the temporary lists incase they're present in them so that we can + // properly perform our place calculation later down the line in this method. + if (leftRelationships.contains(relationship)) { + leftRelationships.remove(relationship); + } + if (rightRelationships.contains(relationship)) { + rightRelationships.remove(relationship); + } context.turnOffAuthorisationSystem(); //If useForPlace for the leftwardType is false for the relationshipType, // we need to sort the relationships here based on leftplace. @@ -145,10 +165,6 @@ public class RelationshipServiceImpl implements RelationshipService { updateItem(context, rightItem); } - - if (isCreation) { - handleCreationPlaces(context, relationship); - } context.restoreAuthSystemState(); } @@ -160,43 +176,14 @@ public class RelationshipServiceImpl implements RelationshipService { 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 findNextLeftPlaceByLeftItem(Context context, Item item) throws SQLException { + return relationshipDAO.findNextLeftPlaceByLeftItem(context, item); } @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); + public int findNextRightPlaceByRightItem(Context context, Item item) throws SQLException { + return relationshipDAO.findNextRightPlaceByRightItem(context, item); } private boolean isRelationshipValidToCreate(Context context, Relationship relationship) throws SQLException { @@ -348,7 +335,7 @@ public class RelationshipServiceImpl implements RelationshipService { if (authorizeService.authorizeActionBoolean(context, relationship.getLeftItem(), Constants.WRITE) || authorizeService.authorizeActionBoolean(context, relationship.getRightItem(), Constants.WRITE)) { relationshipDAO.delete(context, relationship); - updatePlaceInRelationship(context, relationship, false); + updatePlaceInRelationship(context, relationship); } else { throw new AuthorizeException( "You do not have write rights on this relationship's items"); diff --git a/dspace-api/src/main/java/org/dspace/content/dao/ProcessDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/ProcessDAO.java new file mode 100644 index 0000000000..f20225a202 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/dao/ProcessDAO.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.content.dao; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.core.Context; +import org.dspace.core.GenericDAO; +import org.dspace.scripts.Process; + +/** + * This is the Data Access Object for the {@link Process} object + */ +public interface ProcessDAO extends GenericDAO { + + /** + * This method will return all the Process objects in the database in a list and it'll be sorted by script name + * @param context The relevant DSpace context + * @return The list of all Process objects in the database sorted on scriptname + * @throws SQLException If something goes wrong + */ + public List findAllSortByScript(Context context) throws SQLException; + + /** + * This method will return all the Process objects in the database in a list and it'll be sorted by start time. + * The most recent one will be shown first + * @param context The relevant DSpace context + * @return The list of all Process objects in the database sorted by starttime + * @throws SQLException If something goes wrong + */ + public List findAllSortByStartTime(Context context) throws SQLException; + + /** + * Returns a list of all Process objects in the database + * @param context The relevant DSpace context + * @param limit The limit for the amount of Processes returned + * @param offset The offset for the Processes to be returned + * @return The list of all Process objects in the Database + * @throws SQLException If something goes wrong + */ + List findAll(Context context, int limit, int offset) throws SQLException; + + /** + * Returns the total amount of Process objects in the dataase + * @param context The relevant DSpace context + * @return An integer that describes the amount of Process objects in the database + * @throws SQLException If something goes wrong + */ + int countRows(Context context) 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 index 04ea09fa08..fc591bef6d 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/RelationshipDAO.java @@ -51,26 +51,26 @@ public interface RelationshipDAO extends GenericDAO { List findByItem(Context context, Item item, Integer limit, Integer offset) 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 + * This method returns the next leftplace integer to use for a relationship with this item as the leftItem + * * @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 + * @return The next integer to be used for the leftplace of a relationship with the given item + * as a left item * @throws SQLException If something goes wrong */ - int findLeftPlaceByLeftItem(Context context, Item item) throws SQLException; + int findNextLeftPlaceByLeftItem(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 + * This method returns the next rightplace integer to use for a relationship with this item as the rightItem + * * @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 + * @return The next integer to be used for the rightplace of a relationship with the given item + * as a right item * @throws SQLException If something goes wrong */ - int findRightPlaceByRightItem(Context context, Item item) throws SQLException; + int findNextRightPlaceByRightItem(Context context, Item item) throws SQLException; /** * This method returns a list of Relationship objects for the given RelationshipType object. @@ -113,7 +113,7 @@ public interface RelationshipDAO extends GenericDAO { * @throws SQLException If something goes wrong */ List findByItemAndRelationshipType(Context context, Item item, RelationshipType relationshipType, - Integer limit, Integer offset) throws SQLException; + Integer limit, Integer offset) throws SQLException; /** * This method returns a list of Relationship objects for the given RelationshipType object. @@ -157,7 +157,6 @@ public interface RelationshipDAO extends GenericDAO { List findByTypeName(Context context, String typeName, Integer limit, Integer offset) throws SQLException; - /** * Count total number of relationships (rows in relationship table) * diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java index ef3a2d050b..b08a57cbe1 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/CollectionDAOImpl.java @@ -9,7 +9,6 @@ package org.dspace.content.dao.impl; import java.sql.SQLException; import java.util.AbstractMap; -import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -64,11 +63,20 @@ public class CollectionDAOImpl extends AbstractHibernateDSODAO imple public List findAll(Context context, MetadataField order, Integer limit, Integer offset) throws SQLException { StringBuilder query = new StringBuilder(); - query.append("SELECT ").append(Collection.class.getSimpleName()).append(" FROM Collection as ") - .append(Collection.class.getSimpleName()).append(" "); - addMetadataLeftJoin(query, Collection.class.getSimpleName(), Arrays.asList(order)); - addMetadataSortQuery(query, Arrays.asList(order), null); + // The query has to be rather complex because we want to sort the retrieval of Collections based on the title + // We'll join the Collections with the metadata fields on the sortfield specified in the parameters + // then we'll sort on this metadata field (this is usually the title). We're also making sure that the place + // is the lowest place in the metadata fields list so that we avoid the duplication bug + query.append("SELECT c" + + " FROM Collection c" + + " left join c.metadata title on title.metadataField = :sortField and" + + " title.dSpaceObject = c.id and" + + " title.place = (select min(internal.place) " + + "from c.metadata internal " + + "where internal.metadataField = :sortField and" + + " internal.dSpaceObject = c.id)" + + " ORDER BY LOWER(title.value)"); Query hibernateQuery = createQuery(context, query.toString()); if (offset != null) { hibernateQuery.setFirstResult(offset); @@ -76,7 +84,7 @@ public class CollectionDAOImpl extends AbstractHibernateDSODAO imple if (limit != null) { hibernateQuery.setMaxResults(limit); } - hibernateQuery.setParameter(order.toString(), order.getID()); + hibernateQuery.setParameter("sortField", order); return list(hibernateQuery); } diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/ProcessDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/ProcessDAOImpl.java new file mode 100644 index 0000000000..0750c9e6d6 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/ProcessDAOImpl.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.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.dao.ProcessDAO; +import org.dspace.core.AbstractHibernateDAO; +import org.dspace.core.Context; +import org.dspace.scripts.Process; +import org.dspace.scripts.Process_; + +/** + * Implementation class for {@link ProcessDAO} + */ +public class ProcessDAOImpl extends AbstractHibernateDAO implements ProcessDAO { + + @Override + public List findAllSortByScript(Context context) throws SQLException { + + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Process.class); + Root processRoot = criteriaQuery.from(Process.class); + criteriaQuery.select(processRoot); + criteriaQuery.orderBy(criteriaBuilder.asc(processRoot.get(Process_.name))); + + return list(context, criteriaQuery, false, Process.class, -1, -1); + + } + + @Override + public List findAllSortByStartTime(Context context) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Process.class); + Root processRoot = criteriaQuery.from(Process.class); + criteriaQuery.select(processRoot); + criteriaQuery.orderBy(criteriaBuilder.desc(processRoot.get(Process_.startTime)), + criteriaBuilder.desc(processRoot.get(Process_.processId))); + + return list(context, criteriaQuery, false, Process.class, -1, -1); + } + + @Override + public List findAll(Context context, int limit, int offset) throws SQLException { + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Process.class); + Root processRoot = criteriaQuery.from(Process.class); + criteriaQuery.select(processRoot); + + return list(context, criteriaQuery, false, Process.class, limit, offset); + } + + @Override + public int countRows(Context context) throws SQLException { + + CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); + CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Process.class); + Root processRoot = criteriaQuery.from(Process.class); + criteriaQuery.select(processRoot); + + return count(context, criteriaQuery, criteriaBuilder, processRoot); + + } +} + + 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 index 517bcced2a..ed5a4260e8 100644 --- 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 @@ -61,7 +61,7 @@ public class RelationshipDAOImpl extends AbstractHibernateDAO impl } @Override - public int findLeftPlaceByLeftItem(Context context, Item item) throws SQLException { + public int findNextLeftPlaceByLeftItem(Context context, Item item) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, Relationship.class); Root relationshipRoot = criteriaQuery.from(Relationship.class); @@ -70,25 +70,25 @@ public class RelationshipDAOImpl extends AbstractHibernateDAO impl 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(); + return list.get(0).getLeftPlace() + 1; } else { - return 1; + return 0; } } @Override - public int findRightPlaceByRightItem(Context context, Item item) throws SQLException { + public int findNextRightPlaceByRightItem(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()); + list.sort((o1, o2) -> o2.getRightPlace() - o1.getRightPlace()); if (!list.isEmpty()) { - return list.get(0).getLeftPlace(); + return list.get(0).getRightPlace() + 1; } else { - return 1; + return 0; } } 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..10d6613b2a 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,31 @@ 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 updateBitstreamOrder(Context context, Bundle bundle, int from, int to) throws AuthorizeException, + SQLException; + + /** + * Moves a bitstream from its current bundle to a new target bundle + * @param context DSpace Context + * @param targetBundle The target bundle where bitstream will be moved to + * @param bitstream The bitstream being moved + * @throws SQLException if database error + * @throws AuthorizeException if authorization error + * @throws IOException if IO error + */ + public void moveBitstreamToBundle(Context context, Bundle targetBundle, Bitstream bitstream) throws SQLException, + AuthorizeException, IOException; + /** * Changes bitstream order according to the array * 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 index 2c79d56435..97f4924a59 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/RelationshipService.java @@ -74,26 +74,26 @@ public interface RelationshipService extends DSpaceCRUDService { 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 + * This method returns the next leftplace integer to use for a relationship with this item as the leftItem + * * @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 + * @return The next integer to be used for the leftplace of a relationship with the given item + * as a left item * @throws SQLException If something goes wrong */ - int findLeftPlaceByLeftItem(Context context, Item item) throws SQLException; + int findNextLeftPlaceByLeftItem(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 + * This method returns the next rightplace integer to use for a relationship with this item as the rightItem + * * @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 + * @return The next integer to be used for the rightplace of a relationship with the given item + * as a right item * @throws SQLException If something goes wrong */ - int findRightPlaceByRightItem(Context context, Item item) throws SQLException; + int findNextRightPlaceByRightItem(Context context, Item item) throws SQLException; /** * This method returns a list of Relationships for which the leftItem or rightItem is equal to the given @@ -147,10 +147,9 @@ public interface RelationshipService extends DSpaceCRUDService { * @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) + public void updatePlaceInRelationship(Context context, Relationship relationship) throws SQLException, AuthorizeException; /** diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java b/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java index 33adb009bb..c4f88e8edd 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java @@ -13,11 +13,8 @@ import java.util.Iterator; import java.util.UUID; import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.HelpFormatter; -import org.apache.commons.cli.OptionBuilder; import org.apache.commons.cli.Options; -import org.apache.commons.cli.PosixParser; -import org.apache.logging.log4j.Logger; +import org.apache.commons.cli.ParseException; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; @@ -26,103 +23,26 @@ import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.handle.factory.HandleServiceFactory; -import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.scripts.DSpaceRunnable; +import org.springframework.beans.factory.annotation.Autowired; /** * Class used to reindex dspace communities/collections/items into discovery - * - * @author Kevin Van de Velde (kevin at atmire dot com) - * @author Mark Diggory (markd at atmire dot com) - * @author Ben Bosman (ben at atmire dot com) */ -public class IndexClient { +public class IndexClient extends DSpaceRunnable { - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(IndexClient.class); + private Context context; - /** - * Default constructor - */ - private IndexClient() { } + @Autowired + private IndexingService indexer; - /** - * When invoked as a command-line tool, creates, updates, removes content - * from the whole index - * - * @param args the command-line arguments, none used - * @throws SQLException An exception that provides information on a database access error or other errors. - * @throws IOException A general class of exceptions produced by failed or interrupted I/O operations. - * @throws SearchServiceException if something went wrong with querying the solr server - */ - public static void main(String[] args) throws SQLException, IOException, SearchServiceException { + private IndexClientOptions indexClientOptions; - Context context = new Context(Context.Mode.READ_ONLY); - context.turnOffAuthorisationSystem(); - - String usage = "org.dspace.discovery.IndexClient [-cbhf] | [-r ] | [-i ] or nothing to " + - "update/clean an existing index."; - Options options = new Options(); - HelpFormatter formatter = new HelpFormatter(); - CommandLine line = null; - - options.addOption(OptionBuilder - .withArgName("handle to remove") - .hasArg(true) - .withDescription( - "remove an Item, Collection or Community from index based on its handle") - .create("r")); - - options.addOption(OptionBuilder - .withArgName("handle or uuid to add or update") - .hasArg(true) - .withDescription( - "add or update an Item, Collection or Community based on its handle or uuid") - .create("i")); - - options.addOption(OptionBuilder - .isRequired(false) - .withDescription( - "clean existing index removing any documents that no longer exist in the db") - .create("c")); - - options.addOption(OptionBuilder - .isRequired(false) - .withDescription( - "(re)build index, wiping out current one if it exists") - .create("b")); - - options.addOption(OptionBuilder - .isRequired(false) - .withDescription( - "Rebuild the spellchecker, can be combined with -b and -f.") - .create("s")); - - options.addOption(OptionBuilder - .isRequired(false) - .withDescription( - "if updating existing index, force each handle to be reindexed even if uptodate") - .create("f")); - - options.addOption(OptionBuilder - .isRequired(false) - .withDescription( - "print this help message") - .create("h")); - - options.addOption(OptionBuilder.isRequired(false).withDescription( - "optimize search core").create("o")); - - try { - line = new PosixParser().parse(options, args); - } catch (Exception e) { - // automatically generate the help statement - formatter.printHelp(usage, e.getMessage(), options, ""); - System.exit(1); - } - - if (line.hasOption("h")) { - // automatically generate the help statement - formatter.printHelp(usage, options); - System.exit(1); + @Override + public void internalRun() throws Exception { + if (indexClientOptions == IndexClientOptions.HELP) { + printHelp(); + return; } /** Acquire from dspace-services in future */ @@ -130,28 +50,29 @@ public class IndexClient { * new DSpace.getServiceManager().getServiceByName("org.dspace.discovery.SolrIndexer"); */ - IndexingService indexer = DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName( - IndexingService.class.getName(), - IndexingService.class - ); - - if (line.hasOption("r")) { - log.info("Removing " + line.getOptionValue("r") + " from Index"); - indexer.unIndexContent(context, line.getOptionValue("r")); - } else if (line.hasOption("c")) { - log.info("Cleaning Index"); - indexer.cleanIndex(line.hasOption("f")); - } else if (line.hasOption("b")) { - log.info("(Re)building index from scratch."); + if (indexClientOptions == IndexClientOptions.REMOVE) { + handler.logInfo("Removing " + commandLine.getOptionValue("r") + " from Index"); + indexer.unIndexContent(context, commandLine.getOptionValue("r")); + } else if (indexClientOptions == IndexClientOptions.CLEAN) { + handler.logInfo("Cleaning Index"); + indexer.cleanIndex(false); + } else if (indexClientOptions == IndexClientOptions.FORCECLEAN) { + handler.logInfo("Cleaning Index"); + indexer.cleanIndex(true); + } else if (indexClientOptions == IndexClientOptions.BUILD || + indexClientOptions == IndexClientOptions.BUILDANDSPELLCHECK) { + handler.logInfo("(Re)building index from scratch."); indexer.createIndex(context); - checkRebuildSpellCheck(line, indexer); - } else if (line.hasOption("o")) { - log.info("Optimizing search core."); + if (indexClientOptions == IndexClientOptions.BUILDANDSPELLCHECK) { + checkRebuildSpellCheck(commandLine, indexer); + } + } else if (indexClientOptions == IndexClientOptions.OPTIMIZE) { + handler.logInfo("Optimizing search core."); indexer.optimize(); - } else if (line.hasOption('s')) { - checkRebuildSpellCheck(line, indexer); - } else if (line.hasOption('i')) { - final String param = line.getOptionValue('i'); + } else if (indexClientOptions == IndexClientOptions.SPELLCHECK) { + checkRebuildSpellCheck(commandLine, indexer); + } else if (indexClientOptions == IndexClientOptions.INDEX) { + final String param = commandLine.getOptionValue('i'); UUID uuid = null; try { uuid = UUID.fromString(param); @@ -171,24 +92,55 @@ public class IndexClient { } } else { dso = (IndexableObject) HandleServiceFactory.getInstance() - .getHandleService().resolveToObject(context, param); + .getHandleService().resolveToObject(context, param); } if (dso == null) { throw new IllegalArgumentException("Cannot resolve " + param + " to a DSpace object"); } - log.info("Indexing " + param + " force " + line.hasOption("f")); + handler.logInfo("Indexing " + param + " force " + commandLine.hasOption("f")); final long startTimeMillis = System.currentTimeMillis(); - final long count = indexAll(indexer, ContentServiceFactory.getInstance().getItemService(), context, dso); + final long count = indexAll(indexer, ContentServiceFactory.getInstance().getItemService(), context, + dso); final long seconds = (System.currentTimeMillis() - startTimeMillis) / 1000; - log.info("Indexed " + count + " object" + (count > 1 ? "s" : "") + " in " + seconds + " seconds"); - } else { - log.info("Updating and Cleaning Index"); - indexer.cleanIndex(line.hasOption("f")); - indexer.updateIndex(context, line.hasOption("f")); - checkRebuildSpellCheck(line, indexer); + handler.logInfo("Indexed " + count + " object" + (count > 1 ? "s" : "") + " in " + seconds + " seconds"); + } else if (indexClientOptions == IndexClientOptions.UPDATE || + indexClientOptions == IndexClientOptions.UPDATEANDSPELLCHECK) { + handler.logInfo("Updating and Cleaning Index"); + indexer.cleanIndex(false); + indexer.updateIndex(context, false); + if (indexClientOptions == IndexClientOptions.UPDATEANDSPELLCHECK) { + checkRebuildSpellCheck(commandLine, indexer); + } + } else if (indexClientOptions == IndexClientOptions.FORCEUPDATE || + indexClientOptions == IndexClientOptions.FORCEUPDATEANDSPELLCHECK) { + handler.logInfo("Updating and Cleaning Index"); + indexer.cleanIndex(true); + indexer.updateIndex(context, true); + if (indexClientOptions == IndexClientOptions.FORCEUPDATEANDSPELLCHECK) { + checkRebuildSpellCheck(commandLine, indexer); + } } - log.info("Done with indexing"); + handler.logInfo("Done with indexing"); + } + + public void setup() throws ParseException { + try { + context = new Context(Context.Mode.READ_ONLY); + context.turnOffAuthorisationSystem(); + } catch (Exception e) { + throw new ParseException("Unable to create a new DSpace Context: " + e.getMessage()); + } + + indexClientOptions = IndexClientOptions.getIndexClientOption(commandLine); + } + + /** + * Constructor for this class. This will ensure that the Options are created and set appropriately. + */ + private IndexClient() { + Options options = IndexClientOptions.constructOptions(); + this.options = options; } /** @@ -273,13 +225,12 @@ public class IndexClient { * @param line the command line options * @param indexer the solr indexer * @throws SearchServiceException in case of a solr exception - * @throws java.io.IOException passed through + * @throws IOException passed through */ - protected static void checkRebuildSpellCheck(CommandLine line, IndexingService indexer) + protected void checkRebuildSpellCheck(CommandLine line, IndexingService indexer) throws SearchServiceException, IOException { - if (line.hasOption("s")) { - log.info("Rebuilding spell checker."); - indexer.buildSpellCheck(); - } + handler.logInfo("Rebuilding spell checker."); + indexer.buildSpellCheck(); } + } diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexClientOptions.java b/dspace-api/src/main/java/org/dspace/discovery/IndexClientOptions.java new file mode 100644 index 0000000000..4b29fbbf27 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexClientOptions.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.discovery; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Options; + +/** + * This Enum holds all the possible options and combinations for the Index discovery script + */ +public enum IndexClientOptions { + REMOVE, + CLEAN, + FORCECLEAN, + BUILD, + BUILDANDSPELLCHECK, + OPTIMIZE, + SPELLCHECK, + INDEX, + UPDATE, + FORCEUPDATE, + UPDATEANDSPELLCHECK, + FORCEUPDATEANDSPELLCHECK, + HELP; + + /** + * This method resolves the CommandLine parameters to figure out which action the index-discovery script should + * perform + * @param commandLine The relevant CommandLine for the index-discovery script + * @return The index-discovery option to be ran, parsed from the CommandLine + */ + protected static IndexClientOptions getIndexClientOption(CommandLine commandLine) { + if (commandLine.hasOption("h")) { + return IndexClientOptions.HELP; + } else if (commandLine.hasOption("r")) { + return IndexClientOptions.REMOVE; + } else if (commandLine.hasOption("c")) { + if (commandLine.hasOption("f")) { + return IndexClientOptions.FORCECLEAN; + } else { + return IndexClientOptions.CLEAN; + } + } else if (commandLine.hasOption("b")) { + if (commandLine.hasOption("s")) { + return IndexClientOptions.BUILDANDSPELLCHECK; + } else { + return IndexClientOptions.BUILD; + } + } else if (commandLine.hasOption("o")) { + return IndexClientOptions.OPTIMIZE; + } else if (commandLine.hasOption("s")) { + return IndexClientOptions.SPELLCHECK; + } else if (commandLine.hasOption("i")) { + return IndexClientOptions.INDEX; + } else { + if (commandLine.hasOption("f") && commandLine.hasOption("s")) { + return IndexClientOptions.FORCEUPDATEANDSPELLCHECK; + } else if (commandLine.hasOption("f")) { + return IndexClientOptions.FORCEUPDATE; + } else if (commandLine.hasOption("s")) { + return IndexClientOptions.UPDATEANDSPELLCHECK; + } else { + return IndexClientOptions.UPDATE; + } + } + } + + protected static Options constructOptions() { + Options options = new Options(); + + options + .addOption("r", "remove", true, "remove an Item, Collection or Community from index based on its handle"); + options.getOption("r").setType(String.class); + options.addOption("i", "index", true, + "add or update an Item, Collection or Community based on its handle or uuid"); + options.getOption("i").setType(boolean.class); + options.addOption("c", "clean", false, + "clean existing index removing any documents that no longer exist in the db"); + options.getOption("c").setType(boolean.class); + options.addOption("b", "build", false, "(re)build index, wiping out current one if it exists"); + options.getOption("b").setType(boolean.class); + options.addOption("s", "spellchecker", false, "Rebuild the spellchecker, can be combined with -b and -f."); + options.getOption("s").setType(boolean.class); + options.addOption("f", "force", false, + "if updating existing index, force each handle to be reindexed even if uptodate"); + options.getOption("f").setType(boolean.class); + options.addOption("h", "help", false, "print this help message"); + options.getOption("h").setType(boolean.class); + return options; + } +} diff --git a/dspace-api/src/main/java/org/dspace/handle/UpdateHandlePrefix.java b/dspace-api/src/main/java/org/dspace/handle/UpdateHandlePrefix.java index 5710f8cf81..133d3dbc2c 100644 --- a/dspace-api/src/main/java/org/dspace/handle/UpdateHandlePrefix.java +++ b/dspace-api/src/main/java/org/dspace/handle/UpdateHandlePrefix.java @@ -13,11 +13,11 @@ import java.sql.SQLException; import java.util.Iterator; import org.apache.logging.log4j.Logger; +import org.dspace.app.launcher.ScriptLauncher; import org.dspace.content.MetadataValue; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.MetadataValueService; import org.dspace.core.Context; -import org.dspace.discovery.IndexClient; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.handle.service.HandleService; import org.dspace.services.ConfigurationService; @@ -139,7 +139,7 @@ public class UpdateHandlePrefix { try { // Reinitialise the search and browse system - IndexClient.main(new String[] {"-b"}); + ScriptLauncher.main(new String[] {"index-discovery", "-b"}); System.out.println("Browse and search indexes are ready now."); // All done System.out.println("\nAll done successfully. Please check the DSpace logs!\n"); diff --git a/dspace-api/src/main/java/org/dspace/scripts/DSpaceCommandLineParameter.java b/dspace-api/src/main/java/org/dspace/scripts/DSpaceCommandLineParameter.java new file mode 100644 index 0000000000..0615aaace3 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/scripts/DSpaceCommandLineParameter.java @@ -0,0 +1,108 @@ +/** + * 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.scripts; + +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; + +/** + * This class serves as a representation of a command line parameter by holding a String name and a String value + */ +public class DSpaceCommandLineParameter { + private String name; + private String value; + + public static String SEPARATOR = "|||"; + + /** + * This constructor will take a String key and String value and store them in their appriopriate fields + * @param key The String value to be stored as the name of the parameter + * @param value The String value to be stored as the value of the parameter + */ + public DSpaceCommandLineParameter(String key, String value) { + this.name = key; + + if (StringUtils.isBlank(value)) { + this.value = null; + } else { + this.value = value; + } + } + + /** + * This constructors accepts a single parameter String that is defined as e.g. "-c test" and it'll parse this + * String into the key "-c" and value "test" to then call the other constructor with those parameters + * @param parameter The String parameter + */ + protected DSpaceCommandLineParameter(String parameter) { + this(StringUtils.substringBefore(parameter, " "), StringUtils.substringAfter(parameter, " ")); + } + + public String getName() { + return name; + } + + public void setName(String key) { + this.name = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + /** + * Converts the DSpaceCommandLineParameter into a String format by concatenating the value and the name String + * values by separating them with a space + * @return The String representation of a DSpaceCommandlineParameter object + */ + public String toString() { + String stringToReturn = ""; + stringToReturn += getName(); + if (StringUtils.isNotBlank(getValue())) { + stringToReturn += " "; + stringToReturn += getValue(); + } + return stringToReturn; + } + + /** + * This method will convert a list of DSpaceCommandLineParameter objects into a single String. This is done by + * calling the toString() method on each of the DSpaceCommandLineParameter objects in the list and concatenating + * them with the Separator defined in this class + * @param parameterList The list of DSpaceCommandLineParameter objects to be converted into a String + * @return The resulting String + */ + public static String concatenate(List parameterList) { + if (parameterList.isEmpty()) { + return null; + } + return parameterList.stream().map(parameter -> parameter.toString()).collect(Collectors.joining(SEPARATOR)); + } + + /** + * Will return a boolean indicating whether the given param is equal to this object + * @param other The other object + * @return A boolean indicating equality + */ + public boolean equals(Object other) { + if (other == null) { + return false; + } + if (other.getClass() != DSpaceCommandLineParameter.class) { + return false; + } + return StringUtils.equals(this.getName(), ((DSpaceCommandLineParameter) other).getName()) && StringUtils + .equals(this.getValue(), ((DSpaceCommandLineParameter) other).getValue()); + } +} diff --git a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java new file mode 100644 index 0000000000..4ce1c5063a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java @@ -0,0 +1,156 @@ +/** + * 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.scripts; + +import java.sql.SQLException; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.core.Context; +import org.dspace.scripts.handler.DSpaceRunnableHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Required; + +/** + * This abstract class is the class that should be extended by each script. + * it provides the basic variables to be hold by the script as well as the means to initialize, parse and run the script + * Every DSpaceRunnable that is implemented in this way should be defined in the scripts.xml config file as a bean + */ +public abstract class DSpaceRunnable implements Runnable { + + /** + * The name of the script + */ + private String name; + /** + * The description of the script + */ + private String description; + /** + * The CommandLine object for the script that'll hold the information + */ + protected CommandLine commandLine; + /** + * The possible options for this script + */ + protected Options options; + /** + * The handler that deals with this script. This handler can currently either be a RestDSpaceRunnableHandler or + * a CommandlineDSpaceRunnableHandler depending from where the script is called + */ + protected DSpaceRunnableHandler handler; + + @Autowired + private AuthorizeService authorizeService; + + public String getName() { + return name; + } + + @Required + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + @Required + public void setDescription(String description) { + this.description = description; + } + + public Options getOptions() { + return options; + } + + /** + * This method will take the primitive array of String objects that represent the parameters given to the String + * and it'll parse these into a CommandLine object that can be used by the script to retrieve the data + * @param args The primitive array of Strings representing the parameters + * @throws ParseException If something goes wrong + */ + private void parse(String[] args) throws ParseException { + commandLine = new DefaultParser().parse(getOptions(), args); + setup(); + } + + /** + * This method will call upon the {@link DSpaceRunnableHandler#printHelp(Options, String)} method with the script's + * options and name + */ + public void printHelp() { + handler.printHelp(options, name); + } + + + /** + * This is the run() method from the Runnable interface that we implement. This method will handle the running + * of the script and all the database modifications needed for the Process object that resulted from this script + */ + @Override + public void run() { + try { + handler.start(); + internalRun(); + handler.handleCompletion(); + } catch (Exception e) { + handler.handleException(e); + } + } + + private void setHandler(DSpaceRunnableHandler dSpaceRunnableHandler) { + this.handler = dSpaceRunnableHandler; + } + + /** + * This method sets the appropriate DSpaceRunnableHandler depending on where it was ran from and it parses + * the arguments given to the script + * @param args The arguments given to the script + * @param dSpaceRunnableHandler The DSpaceRunnableHandler object that defines from where the script was ran + * @throws ParseException If something goes wrong + */ + public void initialize(String[] args, DSpaceRunnableHandler dSpaceRunnableHandler) throws ParseException { + this.setHandler(dSpaceRunnableHandler); + this.parse(args); + } + + /** + * This method has to be included in every script and this will be the main execution block for the script that'll + * contain all the logic needed + * @throws Exception If something goes wrong + */ + public abstract void internalRun() throws Exception; + + /** + * This method has to be included in every script and handles the setup of the script by parsing the CommandLine + * and setting the variables + * @throws ParseException If something goes wrong + */ + public abstract void setup() throws ParseException; + + /** + * This method will return if the script is allowed to execute in the given context. This is by default set + * to the currentUser in the context being an admin, however this can be overwritten by each script individually + * if different rules apply + * @param context The relevant DSpace context + * @return A boolean indicating whether the script is allowed to execute or not + */ + public boolean isAllowedToExecute(Context context) { + try { + return authorizeService.isAdmin(context); + } catch (SQLException e) { + handler.logError("Error occured when trying to verify permissions for script: " + name); + } + return false; + } +} diff --git a/dspace-api/src/main/java/org/dspace/scripts/Process.java b/dspace-api/src/main/java/org/dspace/scripts/Process.java new file mode 100644 index 0000000000..bc9204d429 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/scripts/Process.java @@ -0,0 +1,242 @@ +/** + * 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.scripts; + +import java.util.Date; +import java.util.List; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.dspace.content.Bitstream; +import org.dspace.content.ProcessStatus; +import org.dspace.core.ReloadableEntity; +import org.dspace.eperson.EPerson; + +/** + * This class is the DB Entity representation of the Process object to be stored in the Database + */ +@Entity +@Table(name = "process") +public class Process implements ReloadableEntity { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "process_id_seq") + @SequenceGenerator(name = "process_id_seq", sequenceName = "process_id_seq", allocationSize = 1) + @Column(name = "process_id", unique = true, nullable = false) + private Integer processId; + + @ManyToOne + @JoinColumn(name = "user_id", nullable = false) + private EPerson ePerson; + + @Column(name = "start_time") + @Temporal(TemporalType.TIMESTAMP) + private Date startTime; + + @Column(name = "finished_time") + @Temporal(TemporalType.TIMESTAMP) + private Date finishedTime; + + @Column(name = "script", nullable = false) + private String name; + + @Column(name = "status") + @Enumerated(EnumType.STRING) + private ProcessStatus processStatus; + + @Column(name = "parameters") + private String parameters; + + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable( + name = "process2bitstream", + joinColumns = {@JoinColumn(name = "process_id")}, + inverseJoinColumns = {@JoinColumn(name = "bitstream_id")} + ) + private List bitstreams; + + @Column(name = "creation_time", nullable = false) + @Temporal(TemporalType.TIMESTAMP) + private Date creationTime; + + protected Process() { + } + + /** + * This method returns the ID that the Process holds within the Database + * @return The ID that the process holds within the database + */ + public Integer getID() { + return processId; + } + + public void setProcessId(Integer processId) { + this.processId = processId; + } + + /** + * This method returns an EPerson object. This EPerson object is the EPerson that initially created the process + * @return The EPerson that created the process + */ + public EPerson getEPerson() { + return ePerson; + } + + public void setEPerson(EPerson ePerson) { + this.ePerson = ePerson; + } + + /** + * This method returns the Start time for the Process. This reflects the time when the Process was actually started + * @return The start time for the Process + */ + public Date getStartTime() { + return startTime; + } + + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + + /** + * This method returns the time that Process was finished + * @return The finished time for the Process + */ + public Date getFinishedTime() { + return finishedTime; + } + + public void setFinishedTime(Date finishedTime) { + this.finishedTime = finishedTime; + } + + /** + * This method returns the name of the Process. For example filter-media + * @return The name of the Process + */ + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * This method returns a ProcessStatus value that represents the current state of the Process. These values + * can be found within the {@link ProcessStatus} enum + * @return The status of the Process + */ + public ProcessStatus getProcessStatus() { + return processStatus; + } + + public void setProcessStatus(ProcessStatus processStatus) { + this.processStatus = processStatus; + } + + /** + * To get the parameters, use ProcessService.getParameters() to get a parsed list of DSpaceCommandLineParameters + * This String representation is the parameter in an unparsed fashion. For example "-c test" + */ + protected String getParameters() { + return parameters; + } + + public void setParameters(String parameters) { + this.parameters = parameters; + } + + /** + * This method returns a list of Bitstreams that will be used or created by the Process. This list contains both + * input and output bitstreams. + * @return The Bitstreams that are used or created by the process + */ + public List getBitstreams() { + return bitstreams; + } + + public void setBitstreams(List bitstreams) { + this.bitstreams = bitstreams; + } + + public void removeBitstream(Bitstream bitstream) { + getBitstreams().remove(bitstream); + } + + public void addBitstream(Bitstream bitstream) { + getBitstreams().add(bitstream); + } + + public void setCreationTime(Date creationTime) { + this.creationTime = creationTime; + } + + /** + * This method will return the time when the Process was created. Note that this is potentially different from + * the StartTime (for example if the Process was queued) + * @return The creation time of the Process + */ + public Date getCreationTime() { + return creationTime; + } + + /** + * Return true if other is the same Process + * as this object, false otherwise + * + * @param other object to compare to + * @return true if object passed in represents the same + * collection as this object + */ + @Override + public boolean equals(Object other) { + return (other instanceof Process && + new EqualsBuilder().append(this.getID(), ((Process) other).getID()) + .append(this.getName(), ((Process) other).getName()) + .append(this.getBitstreams(), ((Process) other).getBitstreams()) + .append(this.getProcessStatus(), ((Process) other).getProcessStatus()) + .append(this.getFinishedTime(), ((Process) other).getFinishedTime()) + .append(this.getStartTime(), ((Process) other).getStartTime()) + .append(this.getParameters(), ((Process) other).getParameters()) + .append(this.getCreationTime(), ((Process) other).getCreationTime()) + .append(this.getEPerson(), ((Process) other).getEPerson()) + .isEquals()); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(this.getID()) + .append(this.getName()) + .append(this.getBitstreams()) + .append(this.getProcessStatus()) + .append(this.getFinishedTime()) + .append(this.getStartTime()) + .append(this.getParameters()) + .append(this.getCreationTime()) + .append(this.getEPerson()) + .toHashCode(); + } +} diff --git a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java new file mode 100644 index 0000000000..cb5a5c9944 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java @@ -0,0 +1,148 @@ +/** + * 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.scripts; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; +import org.dspace.content.ProcessStatus; +import org.dspace.content.dao.ProcessDAO; +import org.dspace.core.Context; +import org.dspace.core.LogManager; +import org.dspace.eperson.EPerson; +import org.dspace.scripts.service.ProcessService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * The implementation for the {@link ProcessService} class + */ +public class ProcessServiceImpl implements ProcessService { + + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ProcessService.class); + + @Autowired + private ProcessDAO processDAO; + + @Override + public Process create(Context context, EPerson ePerson, String scriptName, + List parameters) throws SQLException { + + Process process = new Process(); + process.setEPerson(ePerson); + process.setName(scriptName); + process.setParameters(DSpaceCommandLineParameter.concatenate(parameters)); + process.setCreationTime(new Date()); + Process createdProcess = processDAO.create(context, process); + log.info(LogManager.getHeader(context, "process_create", + "Process has been created for eperson with email " + ePerson.getEmail() + + " with ID " + createdProcess.getID() + " and scriptName " + + scriptName + " and parameters " + parameters)); + return createdProcess; + } + + @Override + public Process find(Context context, int processId) throws SQLException { + return processDAO.findByID(context, Process.class, processId); + } + + @Override + public List findAll(Context context) throws SQLException { + return processDAO.findAll(context, Process.class); + } + + @Override + public List findAll(Context context, int limit, int offset) throws SQLException { + return processDAO.findAll(context, limit, offset); + } + + @Override + public List findAllSortByScript(Context context) throws SQLException { + return processDAO.findAllSortByScript(context); + } + + @Override + public List findAllSortByStartTime(Context context) throws SQLException { + List processes = findAll(context); + Comparator comparing = Comparator + .comparing(Process::getStartTime, Comparator.nullsLast(Comparator.naturalOrder())); + comparing = comparing.thenComparing(Process::getID); + processes.sort(comparing); + return processes; + } + + @Override + public void start(Context context, Process process) throws SQLException { + process.setProcessStatus(ProcessStatus.RUNNING); + process.setStartTime(new Date()); + update(context, process); + log.info(LogManager.getHeader(context, "process_start", "Process with ID " + process.getID() + + " and name " + process.getName() + " has started")); + + } + + @Override + public void fail(Context context, Process process) throws SQLException { + process.setProcessStatus(ProcessStatus.FAILED); + process.setFinishedTime(new Date()); + update(context, process); + log.info(LogManager.getHeader(context, "process_fail", "Process with ID " + process.getID() + + " and name " + process.getName() + " has failed")); + + } + + @Override + public void complete(Context context, Process process) throws SQLException { + process.setProcessStatus(ProcessStatus.COMPLETED); + process.setFinishedTime(new Date()); + update(context, process); + log.info(LogManager.getHeader(context, "process_complete", "Process with ID " + process.getID() + + " and name " + process.getName() + " has been completed")); + + } + + @Override + public void delete(Context context, Process process) throws SQLException { + processDAO.delete(context, process); + log.info(LogManager.getHeader(context, "process_delete", "Process with ID " + process.getID() + + " and name " + process.getName() + " has been deleted")); + + } + + @Override + public void update(Context context, Process process) throws SQLException { + processDAO.save(context, process); + } + + @Override + public List getParameters(Process process) { + if (StringUtils.isBlank(process.getParameters())) { + return Collections.emptyList(); + } + + String[] parameterArray = process.getParameters().split(Pattern.quote(DSpaceCommandLineParameter.SEPARATOR)); + List parameterList = new ArrayList<>(); + + for (String parameter : parameterArray) { + parameterList.add(new DSpaceCommandLineParameter(parameter)); + } + + return parameterList; + } + + public int countTotal(Context context) throws SQLException { + return processDAO.countRows(context); + } + +} diff --git a/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java b/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java new file mode 100644 index 0000000000..e2a6acf3a8 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.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.scripts; + +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.core.Context; +import org.dspace.scripts.service.ScriptService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * The implementation for the {@link ScriptService} + */ +public class ScriptServiceImpl implements ScriptService { + + @Autowired + private List dSpaceRunnables; + + @Override + public DSpaceRunnable getScriptForName(String name) { + return dSpaceRunnables.stream() + .filter(dSpaceRunnable -> StringUtils.equalsIgnoreCase(dSpaceRunnable.getName(), name)) + .findFirst() + .orElse(null); + } + + @Override + public List getDSpaceRunnables(Context context) { + return dSpaceRunnables.stream().filter( + dSpaceRunnable -> dSpaceRunnable.isAllowedToExecute(context)).collect(Collectors.toList()); + } +} diff --git a/dspace-api/src/main/java/org/dspace/scripts/factory/ScriptServiceFactory.java b/dspace-api/src/main/java/org/dspace/scripts/factory/ScriptServiceFactory.java new file mode 100644 index 0000000000..8f24728625 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/scripts/factory/ScriptServiceFactory.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.scripts.factory; + +import org.dspace.scripts.service.ProcessService; +import org.dspace.scripts.service.ScriptService; +import org.dspace.services.factory.DSpaceServicesFactory; + +/** + * Abstract factory to get services for the Script workload, use ScriptServiceFactory.getInstance() to retrieve an + * implementation + * + */ +public abstract class ScriptServiceFactory { + + /** + * This method will return an instance of the ScriptService + * @return An instance of the ScriptService + */ + public abstract ScriptService getScriptService(); + + /** + * This method will return an instance of the ProcessService + * @return An instance of the ProcessService + */ + public abstract ProcessService getProcessService(); + + /** + * Use this method to retrieve an implementation of the ScriptServiceFactory to use to retrieve the different beans + * @return An implementation of the ScriptServiceFactory + */ + public static ScriptServiceFactory getInstance() { + return DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName("scriptServiceFactory", ScriptServiceFactory.class); + } +} diff --git a/dspace-api/src/main/java/org/dspace/scripts/factory/impl/ScriptServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/scripts/factory/impl/ScriptServiceFactoryImpl.java new file mode 100644 index 0000000000..ba8cd1a784 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/scripts/factory/impl/ScriptServiceFactoryImpl.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.scripts.factory.impl; + +import org.dspace.scripts.factory.ScriptServiceFactory; +import org.dspace.scripts.service.ProcessService; +import org.dspace.scripts.service.ScriptService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * The implementation for the {@link ScriptServiceFactory} + */ +public class ScriptServiceFactoryImpl extends ScriptServiceFactory { + + @Autowired(required = true) + private ScriptService scriptService; + + @Autowired(required = true) + private ProcessService processService; + + @Override + public ScriptService getScriptService() { + return scriptService; + } + + @Override + public ProcessService getProcessService() { + return processService; + } +} diff --git a/dspace-api/src/main/java/org/dspace/scripts/handler/DSpaceRunnableHandler.java b/dspace-api/src/main/java/org/dspace/scripts/handler/DSpaceRunnableHandler.java new file mode 100644 index 0000000000..01ca2fafd9 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/scripts/handler/DSpaceRunnableHandler.java @@ -0,0 +1,81 @@ +/** + * 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.scripts.handler; + +import java.sql.SQLException; + +import org.apache.commons.cli.Options; + +/** + * This is an interface meant to be implemented by any DSpaceRunnableHandler to specify specific execution methods + * of the script depending on where it was called from + */ +public interface DSpaceRunnableHandler { + + /** + * This method handles the start of the script + * @throws SQLException If something goes wrong + */ + public void start() throws SQLException; + + /** + * This method handles the completion of the script + * @throws SQLException If something goes wrong + */ + public void handleCompletion() throws SQLException; + + /** + * This method handles an exception thrown by the script + * @param e The exception thrown by the script + */ + public void handleException(Exception e); + + /** + * This method handles an exception thrown by the script + * @param message The String message for the exception thrown by the script + */ + public void handleException(String message); + + /** + * This method handles an exception thrown by the script + * @param message The String message for the exception thrown by the script + * @param e The exception thrown by the script + */ + public void handleException(String message, Exception e); + + /** + * This method will perform the debug logging of the message given + * @param message The message to be logged as debug + */ + public void logDebug(String message); + + /** + * This method will perform the info logging of the message given + * @param message The message to be logged as info + */ + public void logInfo(String message); + + /** + * This method will perform the warning logging of the message given + * @param message The message to be logged as warning + */ + public void logWarning(String message); + + /** + * This method will perform the error logging of the message given + * @param message The message to be logged as an error + */ + public void logError(String message); + + /** + * This method will print the help for the options and name + * @param options The options for the script + * @param name The name of the script + */ + public void printHelp(Options options, String name); +} diff --git a/dspace-api/src/main/java/org/dspace/scripts/handler/impl/CommandLineDSpaceRunnableHandler.java b/dspace-api/src/main/java/org/dspace/scripts/handler/impl/CommandLineDSpaceRunnableHandler.java new file mode 100644 index 0000000000..97925c1843 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/scripts/handler/impl/CommandLineDSpaceRunnableHandler.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.scripts.handler.impl; + +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Options; +import org.apache.logging.log4j.Logger; +import org.dspace.scripts.handler.DSpaceRunnableHandler; + +/** + * This is an implementation for the CommandLineDSpaceRunnables which means that these implementations + * are used by DSpaceRunnables which are called from the CommandLine + */ +public class CommandLineDSpaceRunnableHandler implements DSpaceRunnableHandler { + private static final Logger log = org.apache.logging.log4j.LogManager + .getLogger(CommandLineDSpaceRunnableHandler.class); + + @Override + public void start() { + System.out.println("The script has started"); + } + + @Override + public void handleCompletion() { + System.out.println("The script has completed"); + } + + @Override + public void handleException(Exception e) { + handleException(null, e); + } + + @Override + public void handleException(String message) { + handleException(message, null); + } + + @Override + public void handleException(String message, Exception e) { + if (message != null) { + System.err.println(message); + log.error(message); + } + if (e != null) { + e.printStackTrace(); + log.error(e.getMessage(), e); + } + + System.exit(1); + } + + @Override + public void logDebug(String message) { + log.debug(message); + } + + @Override + public void logInfo(String message) { + System.out.println(message); + log.info(message); + } + + @Override + public void logWarning(String message) { + System.out.println(message); + log.warn(message); + } + + @Override + public void logError(String message) { + System.err.println(message); + log.error(message); + } + + @Override + public void printHelp(Options options, String name) { + if (options != null) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp(name, options); + } + } +} diff --git a/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java b/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java new file mode 100644 index 0000000000..e277ab32f4 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java @@ -0,0 +1,139 @@ +/** + * 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.scripts.service; + +import java.sql.SQLException; +import java.util.List; + +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.scripts.DSpaceCommandLineParameter; +import org.dspace.scripts.Process; + +/** + * An interface for the ProcessService with methods regarding the Process workload + */ +public interface ProcessService { + + /** + * This method will create a Process object in the database + * @param context The relevant DSpace context + * @param ePerson The ePerson for which this process will be created on + * @param scriptName The script name to be used for the process + * @param parameters The parameters to be used for the process + * @return The created process + * @throws SQLException If something goes wrong + */ + public Process create(Context context, EPerson ePerson, String scriptName, + List parameters) throws SQLException; + + /** + * This method will retrieve a Process object from the Database with the given ID + * @param context The relevant DSpace context + * @param processId The process id on which we'll search for in the database + * @return The process that holds the given process id + * @throws SQLException If something goes wrong + */ + public Process find(Context context, int processId) throws SQLException; + + /** + * Returns a list of all Process objects in the database + * @param context The relevant DSpace context + * @return The list of all Process objects in the Database + * @throws SQLException If something goes wrong + */ + public List findAll(Context context) throws SQLException; + + /** + * Returns a list of all Process objects in the database + * @param context The relevant DSpace context + * @param limit The limit for the amount of Processes returned + * @param offset The offset for the Processes to be returned + * @return The list of all Process objects in the Database + * @throws SQLException If something goes wrong + */ + public List findAll(Context context, int limit, int offset) throws SQLException; + + + /** + * Returns a list of all Process objects in the database sorted by script name + * @param context The relevant DSpace context + * @return The list of all Process objects in the database sorted by script name + * @throws SQLException If something goes wrong + */ + public List findAllSortByScript(Context context) throws SQLException; + + /** + * Returns a list of all Process objects in the database sorted by start time + * The most recent one will be shown first + * @param context The relevant DSpace context + * @return The list of all Process objects sorted by start time + * @throws SQLException If something goes wrong + */ + public List findAllSortByStartTime(Context context) throws SQLException; + + /** + * This method will perform the logic needed to update the Process object in the database to represent a + * started state. A started state refers to {@link org.dspace.content.ProcessStatus#RUNNING} + * @param context The relevant DSpace context + * @param process The Process object to be updated + * @throws SQLException If something goes wrong + */ + public void start(Context context, Process process) throws SQLException; + + /** + * This method will perform the logic needed to update the Process object in the database to represent + * a failed state + * @param context The relevant DSpace context + * @param process The Process object to be updated + * @throws SQLException If something goes wrong + */ + public void fail(Context context, Process process) throws SQLException; + + /** + * This method will perform the logic needed to update the Process object in the database to represent + * a complete state + * @param context The relevant DSpace context + * @param process The Process object to be updated + * @throws SQLException If something goes wrong + */ + public void complete(Context context, Process process) throws SQLException; + + /** + * This method will delete the given Process object from the database + * @param context The relevant DSpace context + * @param process The Process object to be deleted + * @throws SQLException If something goes wrong + */ + public void delete(Context context, Process process) throws SQLException; + + /** + * This method will be used to update the given Process object in the database + * @param context The relevant DSpace context + * @param process The Process object to be updated + * @throws SQLException If something goes wrong + */ + public void update(Context context, Process process) throws SQLException; + + /** + * This method will retrieve the list of parameters from the Process in its String format and it will parse + * these parameters to a list of {@link DSpaceCommandLineParameter} objects for better usability throughout DSpace + * @param process The Process object for which we'll return the parameters + * @return The list of parsed parameters from the Process object + */ + public List getParameters(Process process); + + /** + * Returns the total amount of Process objects in the dataase + * @param context The relevant DSpace context + * @return An integer that describes the amount of Process objects in the database + * @throws SQLException If something goes wrong + */ + int countTotal(Context context) throws SQLException; + +} diff --git a/dspace-api/src/main/java/org/dspace/scripts/service/ScriptService.java b/dspace-api/src/main/java/org/dspace/scripts/service/ScriptService.java new file mode 100644 index 0000000000..fc680bd612 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/scripts/service/ScriptService.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.scripts.service; + +import java.util.List; + +import org.dspace.core.Context; +import org.dspace.scripts.DSpaceRunnable; + +/** + * This service will deal with logic to handle DSpaceRunnable objects + */ +public interface ScriptService { + + /** + * This method will return the DSpaceRunnable that has the name that's equal to the name given in the parameters + * @param name The name that the script has to match + * @return The matching DSpaceRunnable script + */ + DSpaceRunnable getScriptForName(String name); + + /** + * This method will return a list of DSpaceRunnable objects for which the given Context is authorized to use them + * @param context The relevant DSpace context + * @return The list of accessible DSpaceRunnable scripts for this context + */ + List getDSpaceRunnables(Context context); +} diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V6.0_2015.03.07__DS-2701_Hibernate_migration.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V6.0_2015.03.07__DS-2701_Hibernate_migration.sql index 94c30bbf67..292d75488d 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V6.0_2015.03.07__DS-2701_Hibernate_migration.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V6.0_2015.03.07__DS-2701_Hibernate_migration.sql @@ -189,16 +189,19 @@ ALTER TABLE item2bundle add primary key (item_id,bundle_id); --Migrate Bundle2Bitsteam ALTER TABLE bundle2bitstream ALTER COLUMN bundle_id rename to bundle_legacy_id; ALTER TABLE bundle2bitstream ALTER COLUMN bitstream_id rename to bitstream_legacy_id; +ALTER TABLE bundle2bitstream ALTER COLUMN bitstream_order rename to bitstream_order_legacy; ALTER TABLE bundle2bitstream ADD COLUMN bundle_id UUID NOT NULL; ALTER TABLE bundle2bitstream ADD CONSTRAINT bundle2bitstream_bundle_id_fk FOREIGN KEY (bundle_id) REFERENCES bundle; ALTER TABLE bundle2bitstream ADD COLUMN bitstream_id UUID NOT NULL; ALTER TABLE bundle2bitstream ADD CONSTRAINT bundle2bitstream_bitstream_id_fk FOREIGN KEY (bitstream_id) REFERENCES bitstream; +ALTER TABLE bundle2bitstream ADD COLUMN bitstream_order INTEGER NOT NULL; UPDATE bundle2bitstream SET bundle_id = (SELECT bundle.uuid FROM bundle WHERE bundle2bitstream.bundle_legacy_id = bundle.bundle_id); UPDATE bundle2bitstream SET bitstream_id = (SELECT bitstream.uuid FROM bitstream WHERE bundle2bitstream.bitstream_legacy_id = bitstream.bitstream_id); ALTER TABLE bundle2bitstream DROP COLUMN bundle_legacy_id; ALTER TABLE bundle2bitstream DROP COLUMN bitstream_legacy_id; +ALTER TABLE bundle2bitstream DROP COLUMN bitstream_order_legacy; ALTER TABLE bundle2bitstream DROP COLUMN id; -ALTER TABLE bundle2bitstream add primary key (bitstream_id,bundle_id); +ALTER TABLE bundle2bitstream add primary key (bitstream_id,bundle_id,bitstream_order); -- Migrate item diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2019_06_14__scripts-and-process.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2019_06_14__scripts-and-process.sql new file mode 100644 index 0000000000..d6e9f2a186 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.0_2019_06_14__scripts-and-process.sql @@ -0,0 +1,40 @@ +-- +-- 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/ +-- =============================================================== +CREATE SEQUENCE process_id_seq; + +CREATE TABLE process +( + process_id INTEGER NOT NULL PRIMARY KEY, + user_id UUID NOT NULL, + start_time TIMESTAMP, + finished_time TIMESTAMP, + creation_time TIMESTAMP NOT NULL, + script VARCHAR(256) NOT NULL, + status VARCHAR(32), + parameters VARCHAR(512) +); + +CREATE TABLE process2bitstream +( + process_id INTEGER REFERENCES process(process_id), + bitstream_id UUID REFERENCES bitstream(uuid), + CONSTRAINT PK_process2bitstream PRIMARY KEY (process_id, bitstream_id) +); + +CREATE INDEX process_user_id_idx ON process(user_id); +CREATE INDEX process_status_idx ON process(status); +CREATE INDEX process_name_idx on process(script); +CREATE INDEX process_start_time_idx on process(start_time); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019_06_14__scripts-and-process.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019_06_14__scripts-and-process.sql new file mode 100644 index 0000000000..a7015e3033 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.0_2019_06_14__scripts-and-process.sql @@ -0,0 +1,40 @@ +-- +-- 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/ +-- =============================================================== +CREATE SEQUENCE process_id_seq; + +CREATE TABLE process +( + process_id INTEGER NOT NULL PRIMARY KEY, + user_id RAW(16) NOT NULL, + start_time TIMESTAMP, + finished_time TIMESTAMP, + creation_time TIMESTAMP NOT NULL, + script VARCHAR(256) NOT NULL, + status VARCHAR(32), + parameters VARCHAR(512) +); + +CREATE TABLE process2bitstream +( + process_id INTEGER REFERENCES process(process_id), + bitstream_id RAW(16) REFERENCES bitstream(uuid), + CONSTRAINT PK_process2bitstream PRIMARY KEY (process_id, bitstream_id) +); + +CREATE INDEX process_user_id_idx ON process(user_id); +CREATE INDEX process_status_idx ON process(status); +CREATE INDEX process_name_idx on process(script); +CREATE INDEX process_start_time_idx on process(start_time); \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2019_06_14__scripts-and-process.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2019_06_14__scripts-and-process.sql new file mode 100644 index 0000000000..d6e9f2a186 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.0_2019_06_14__scripts-and-process.sql @@ -0,0 +1,40 @@ +-- +-- 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/ +-- =============================================================== +CREATE SEQUENCE process_id_seq; + +CREATE TABLE process +( + process_id INTEGER NOT NULL PRIMARY KEY, + user_id UUID NOT NULL, + start_time TIMESTAMP, + finished_time TIMESTAMP, + creation_time TIMESTAMP NOT NULL, + script VARCHAR(256) NOT NULL, + status VARCHAR(32), + parameters VARCHAR(512) +); + +CREATE TABLE process2bitstream +( + process_id INTEGER REFERENCES process(process_id), + bitstream_id UUID REFERENCES bitstream(uuid), + CONSTRAINT PK_process2bitstream PRIMARY KEY (process_id, bitstream_id) +); + +CREATE INDEX process_user_id_idx ON process(user_id); +CREATE INDEX process_status_idx ON process(status); +CREATE INDEX process_name_idx on process(script); +CREATE INDEX process_start_time_idx on process(start_time); \ No newline at end of file diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml new file mode 100644 index 0000000000..b28d45ec18 --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml b/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml index 81ab1f1c4f..e0ec2dc60a 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml @@ -14,11 +14,12 @@ - + - - + + +
@@ -53,6 +54,7 @@ isAuthorOfPublication personConfiguration true + true Add an author diff --git a/dspace-api/src/test/java/org/dspace/content/RelationshipMetadataServiceTest.java b/dspace-api/src/test/java/org/dspace/content/RelationshipMetadataServiceTest.java index 33d44cd35c..0502f516fd 100644 --- a/dspace-api/src/test/java/org/dspace/content/RelationshipMetadataServiceTest.java +++ b/dspace-api/src/test/java/org/dspace/content/RelationshipMetadataServiceTest.java @@ -354,4 +354,78 @@ public class RelationshipMetadataServiceTest extends AbstractUnitTest { assertThat(issueList.size(), equalTo(1)); assertThat(issueList.get(0).getValue(), equalTo("2")); } + + @Test + public void testGetNextRightPlace() throws Exception { + assertThat(relationshipService.findNextRightPlaceByRightItem(context, authorItem), equalTo(0)); + context.turnOffAuthorisationSystem(); + + itemService.addMetadata(context, item, "relationship", "type", null, null, "Publication"); + itemService.addMetadata(context, authorItem, "relationship", "type", null, null, "Author"); + itemService.addMetadata(context, authorItem, "person", "familyName", null, null, "familyName"); + itemService.addMetadata(context, authorItem, "person", "givenName", null, null, "firstName"); + EntityType publicationEntityType = entityTypeService.create(context, "Publication"); + EntityType authorEntityType = entityTypeService.create(context, "Author"); + RelationshipType isAuthorOfPublication = relationshipTypeService + .create(context, publicationEntityType, authorEntityType, "isAuthorOfPublication", "isPublicationOfAuthor", + null, null, null, null); + Relationship relationship = relationshipService.create(context, item, authorItem, isAuthorOfPublication, 0, 0); + context.restoreAuthSystemState(); + + assertThat(relationshipService.findNextRightPlaceByRightItem(context, authorItem), equalTo(1)); + + context.turnOffAuthorisationSystem(); + Community community = communityService.create(null, context); + + Collection col = collectionService.create(context, community); + WorkspaceItem is = workspaceItemService.create(context, col, false); + Item secondItem = installItemService.installItem(context, is); + itemService.addMetadata(context, secondItem, "relationship", "type", null, null, "Publication"); + Relationship secondRelationship = relationshipService.create(context, secondItem, authorItem, + isAuthorOfPublication, 0, 0); + context.restoreAuthSystemState(); + + assertThat(relationshipService.findNextRightPlaceByRightItem(context, authorItem), equalTo(2)); + + + + } + + @Test + public void testGetNextLeftPlace() throws Exception { + assertThat(relationshipService.findNextLeftPlaceByLeftItem(context, item), equalTo(0)); + context.turnOffAuthorisationSystem(); + + itemService.addMetadata(context, item, "relationship", "type", null, null, "Publication"); + itemService.addMetadata(context, authorItem, "relationship", "type", null, null, "Author"); + itemService.addMetadata(context, authorItem, "person", "familyName", null, null, "familyName"); + itemService.addMetadata(context, authorItem, "person", "givenName", null, null, "firstName"); + EntityType publicationEntityType = entityTypeService.create(context, "Publication"); + EntityType authorEntityType = entityTypeService.create(context, "Author"); + RelationshipType isAuthorOfPublication = relationshipTypeService + .create(context, publicationEntityType, authorEntityType, "isAuthorOfPublication", "isPublicationOfAuthor", + null, null, null, null); + Relationship relationship = relationshipService.create(context, item, authorItem, isAuthorOfPublication, 0, 0); + context.restoreAuthSystemState(); + + assertThat(relationshipService.findNextLeftPlaceByLeftItem(context, item), equalTo(1)); + + context.turnOffAuthorisationSystem(); + Community community = communityService.create(null, context); + + Collection col = collectionService.create(context, community); + WorkspaceItem is = workspaceItemService.create(context, col, false); + Item secondAuthor = installItemService.installItem(context, is); + itemService.addMetadata(context, secondAuthor, "relationship", "type", null, null, "Author"); + itemService.addMetadata(context, secondAuthor, "person", "familyName", null, null, "familyName"); + itemService.addMetadata(context, secondAuthor, "person", "givenName", null, null, "firstName"); + Relationship secondRelationship = relationshipService.create(context, item, secondAuthor, + isAuthorOfPublication, 0, 0); + context.restoreAuthSystemState(); + + assertThat(relationshipService.findNextLeftPlaceByLeftItem(context, item), equalTo(2)); + + + + } } diff --git a/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplPlaceTest.java b/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplPlaceTest.java new file mode 100644 index 0000000000..1c5c30f939 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplPlaceTest.java @@ -0,0 +1,428 @@ +/** + * 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 static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +import java.sql.SQLException; +import java.util.List; + +import org.apache.logging.log4j.Logger; +import org.dspace.AbstractUnitTest; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.factory.ContentServiceFactory; +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.MetadataValueService; +import org.dspace.content.service.RelationshipService; +import org.dspace.content.service.RelationshipTypeService; +import org.dspace.content.service.WorkspaceItemService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class RelationshipServiceImplPlaceTest extends AbstractUnitTest { + + private static final Logger log = org.apache.logging.log4j.LogManager + .getLogger(RelationshipServiceImplPlaceTest.class); + + protected RelationshipService relationshipService = ContentServiceFactory.getInstance().getRelationshipService(); + protected RelationshipTypeService relationshipTypeService = ContentServiceFactory.getInstance() + .getRelationshipTypeService(); + protected EntityTypeService entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService(); + protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); + protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); + protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + protected InstallItemService installItemService = ContentServiceFactory.getInstance().getInstallItemService(); + protected WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); + protected MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService(); + + Community community; + Collection col; + + Item item; + Item authorItem; + RelationshipType isAuthorOfPublication; + EntityType publicationEntityType; + EntityType authorEntityType; + + String authorQualifier = "author"; + String contributorElement = "contributor"; + String dcSchema = "dc"; + + /** + * This method will be run before every test as per @Before. It will + * initialize resources required for the tests. + */ + @Before + @Override + public void init() { + super.init(); + try { + context.turnOffAuthorisationSystem(); + community = communityService.create(null, context); + + col = collectionService.create(context, community); + WorkspaceItem is = workspaceItemService.create(context, col, false); + WorkspaceItem authorIs = workspaceItemService.create(context, col, false); + + item = installItemService.installItem(context, is); + itemService.addMetadata(context, item, "relationship", "type", null, null, "Publication"); + + authorItem = installItemService.installItem(context, authorIs); + itemService.addMetadata(context, authorItem, "relationship", "type", null, null, "Person"); + itemService.addMetadata(context, authorItem, "person", "familyName", null, null, "familyName"); + itemService.addMetadata(context, authorItem, "person", "givenName", null, null, "firstName"); + + publicationEntityType = entityTypeService.create(context, "Publication"); + authorEntityType = entityTypeService.create(context, "Person"); + isAuthorOfPublication = relationshipTypeService + .create(context, publicationEntityType, authorEntityType, + "isAuthorOfPublication", "isPublicationOfAuthor", + null, null, null, null); + + context.restoreAuthSystemState(); + } catch (AuthorizeException ex) { + log.error("Authorization Error in init", ex); + fail("Authorization Error in init: " + ex.getMessage()); + } catch (SQLException ex) { + log.error("SQL Error in init", ex); + fail("SQL Error in init: " + ex.getMessage()); + } + } + + /** + * This method will be run after every test as per @After. It will + * clean resources initialized by the @Before methods. + */ + @After + @Override + public void destroy() { + context.abort(); + super.destroy(); + } + + /** + * This test will test the use case of having an item to which we add some metadata. After that, we'll add a single + * relationship to this Item. We'll test whether the places are correct, in this case it'll be the two metadata + * values that we created first, has to have place 0 and 1. The Relationship that we just created needs to have + * leftPlace 2 and the metadata value resulting from that Relationship needs to also have place 2. + * Once these assertions succeed, we basically repeat said process with new metadata values and a new relationship. + * We then test if the old assertions still hold true like they should and that the new ones behave as expected + * as well. + * @throws Exception If something goes wrong + */ + @Test + public void addMetadataAndRelationshipTest() throws Exception { + context.turnOffAuthorisationSystem(); + + // Here we add the first set of metadata to the item + itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, one"); + itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, two"); + + // Here we create the first Relationship to the item + Relationship relationship = relationshipService + .create(context, item, authorItem, isAuthorOfPublication, -1, -1); + + context.restoreAuthSystemState(); + + // The code below performs the mentioned assertions to ensure the place is correct + List list = itemService + .getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY); + assertThat(list.size(), equalTo(3)); + + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, one", null, 0, list.get(0)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, two", null, 1, list.get(1)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "familyName, firstName", + "virtual::" + relationship.getID(), 2, list.get(2)); + assertThat(relationship.getLeftPlace(), equalTo(2)); + + context.turnOffAuthorisationSystem(); + + // This is where we add the second set of metadata values + itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, three"); + itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, four"); + + // Here we create an Item so that we can create another relationship with this item + WorkspaceItem authorIs = workspaceItemService.create(context, col, false); + Item secondAuthorItem = installItemService.installItem(context, authorIs); + itemService.addMetadata(context, secondAuthorItem, "relationship", "type", null, null, "Person"); + itemService.addMetadata(context, secondAuthorItem, "person", "familyName", null, null, "familyNameTwo"); + itemService.addMetadata(context, secondAuthorItem, "person", "givenName", null, null, "firstNameTwo"); + Relationship relationshipTwo = relationshipService + .create(context, item, secondAuthorItem, isAuthorOfPublication, -1, -1); + + context.restoreAuthSystemState(); + + // Here we retrieve the list of metadata again to perform the assertions on the places below as mentioned + list = itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY); + + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, one", null, 0, list.get(0)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, two", null, 1, list.get(1)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "familyName, firstName", + "virtual::" + relationship.getID(), 2, list.get(2)); + assertThat(relationship.getLeftPlace(), equalTo(2)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, three", null, 3, list.get(3)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, four", null, 4, list.get(4)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "familyNameTwo, firstNameTwo", + "virtual::" + relationshipTwo.getID(), 5, list.get(5)); + assertThat(relationshipTwo.getLeftPlace(), equalTo(5)); + + } + + /** + * This test is virtually the same as above, only this time we'll add Relationships with leftPlaces already set + * equal to what they HAVE to be. So in the first test addMetadataAndRelationshipTest, we didn't specify a place + * and left it up to the Service to determine it, here we provide a correct place already. + * We perform the exact same logic except that we give a proper place already to the Relationships and we + * perform the same checks + * @throws Exception If something goes wrong + */ + @Test + public void AddMetadataAndRelationshipWithSpecificPlaceTest() throws Exception { + context.turnOffAuthorisationSystem(); + + // Here we add the first set of metadata to the item + itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, one"); + itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, two"); + + // Here we create the first Relationship to the item with the specific leftPlace: 2 + Relationship relationship = relationshipService.create(context, item, authorItem, isAuthorOfPublication, 2, -1); + + context.restoreAuthSystemState(); + + // The code below performs the mentioned assertions to ensure the place is correct + List list = itemService + .getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY); + assertThat(list.size(), equalTo(3)); + + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, one", null, 0, list.get(0)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, two", null, 1, list.get(1)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "familyName, firstName", + "virtual::" + relationship.getID(), 2, list.get(2)); + assertThat(relationship.getLeftPlace(), equalTo(2)); + + context.turnOffAuthorisationSystem(); + + // This is where we add the second set of metadata values + itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, three"); + itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, four"); + + // Here we create an Item so that we can create another relationship with this item. We'll give this + // Relationship a specific place as well + WorkspaceItem authorIs = workspaceItemService.create(context, col, false); + Item secondAuthorItem = installItemService.installItem(context, authorIs); + itemService.addMetadata(context, secondAuthorItem, "relationship", "type", null, null, "Person"); + itemService.addMetadata(context, secondAuthorItem, "person", "familyName", null, null, "familyNameTwo"); + itemService.addMetadata(context, secondAuthorItem, "person", "givenName", null, null, "firstNameTwo"); + Relationship relationshipTwo = relationshipService + .create(context, item, secondAuthorItem, isAuthorOfPublication, 5, -1); + + context.restoreAuthSystemState(); + + // Here we retrieve the list of metadata again to perform the assertions on the places below as mentioned + list = itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY); + + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, one", null, 0, list.get(0)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, two", null, 1, list.get(1)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "familyName, firstName", + "virtual::" + relationship.getID(), 2, list.get(2)); + assertThat(relationship.getLeftPlace(), equalTo(2)); + + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, three", null, 3, list.get(3)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, four", null, 4, list.get(4)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "familyNameTwo, firstNameTwo", + "virtual::" + relationshipTwo.getID(), 5, list.get(5)); + assertThat(relationshipTwo.getLeftPlace(), equalTo(5)); + + } + + + /** + * In this test, our goal will be to add a bunch of metadata values to then remove one of them. We'll check the list + * of metadata values for the item and check that the places have not been altered for the metadata values IF an + * item.update hadn't been called yet. We'll then create a Relationship (by which an item.update will be called) + * and then we check that the places are set correctly. + * We then repeat this process once more and check that everything works as intended + * @throws Exception + */ + @Test + public void AddAndRemoveMetadataAndRelationshipsTest() throws Exception { + context.turnOffAuthorisationSystem(); + + // Here we add the first set of metadata to the item + itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, one"); + itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, two"); + itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, three"); + + // Get a specific metadatavlaue to remove + MetadataValue metadataValueToRemove = itemService.getMetadata(item, "dc", "contributor", "author", Item.ANY) + .get(1); + // Remove the actual metadata value + item.removeMetadata(metadataValueToRemove); + metadataValueService.delete(context, metadataValueToRemove); + + context.restoreAuthSystemState(); + + // Retrieve the list of mdv again + List list = itemService + .getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY); + // Verify we only have 2 mdv left + assertThat(list.size(), equalTo(2)); + + // Check that these places are still intact after the deletion as the place doesn't get updated until an + // item.update has been called + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, one", null, 0, list.get(0)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, three", null, 2, list.get(1)); + + context.turnOffAuthorisationSystem(); + + // Create a relationship with this item with a spcific place + Relationship relationship = relationshipService.create(context, item, authorItem, isAuthorOfPublication, 1, -1); + + context.restoreAuthSystemState(); + + // Retrieve the list again and verify that the creation of the Relationship added an additional mdv + list = itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY); + assertThat(list.size(), equalTo(3)); + + // Assert that the mdv are well placed + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, one", null, 0, list.get(0)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "familyName, firstName", + "virtual::" + relationship.getID(), 1, list.get(1)); + assertThat(relationship.getLeftPlace(), equalTo(1)); + + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, three", null, 2, list.get(2)); + + context.turnOffAuthorisationSystem(); + + // Add two extra mdv + itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, four"); + itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, five"); + + //This is author "test, four" that we're removing + metadataValueToRemove = itemService.getMetadata(item, "dc", "contributor", "author", Item.ANY).get(3); + item.removeMetadata(metadataValueToRemove); + metadataValueService.delete(context, metadataValueToRemove); + + context.restoreAuthSystemState(); + + list = itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY); + + // Check that these places are still intact after the deletion as the place doesn't get updated until an + // item.update has been called + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, one", null, 0, list.get(0)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "familyName, firstName", + "virtual::" + relationship.getID(), 1, list.get(1)); + assertThat(relationship.getLeftPlace(), equalTo(1)); + + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, three", null, 2, list.get(2)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, five", null, 4, list.get(3)); + + context.turnOffAuthorisationSystem(); + + // Create an additional item for another relationship + WorkspaceItem authorIs = workspaceItemService.create(context, col, false); + Item secondAuthorItem = installItemService.installItem(context, authorIs); + itemService.addMetadata(context, secondAuthorItem, "relationship", "type", null, null, "Person"); + itemService.addMetadata(context, secondAuthorItem, "person", "familyName", null, null, "familyNameTwo"); + itemService.addMetadata(context, secondAuthorItem, "person", "givenName", null, null, "firstNameTwo"); + Relationship relationshipTwo = relationshipService + .create(context, item, secondAuthorItem, isAuthorOfPublication, 3, -1); + + context.restoreAuthSystemState(); + + // Check that the other mdv are still okay and that the creation of the relationship added + // another correct mdv to the item + list = itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY); + + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, one", null, 0, list.get(0)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "familyName, firstName", + "virtual::" + relationship.getID(), 1, list.get(1)); + assertThat(relationship.getLeftPlace(), equalTo(1)); + + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, three", null, 2, list.get(2)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "familyNameTwo, firstNameTwo", + "virtual::" + relationshipTwo.getID(), 3, list.get(3)); + assertThat(relationshipTwo.getLeftPlace(), equalTo(3)); + + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, five", null, 4, list.get(4)); + + } + + @Test + public void AddAndUpdateMetadataAndRelationshipsTest() throws Exception { + context.turnOffAuthorisationSystem(); + + // Add metadata and relationships to the item + itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, one"); + itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, two"); + itemService.addMetadata(context, item, dcSchema, contributorElement, authorQualifier, null, "test, three"); + + Relationship relationship = relationshipService + .create(context, item, authorItem, isAuthorOfPublication, -1, -1); + + context.restoreAuthSystemState(); + + // Get the list of mdv and assert that they're correct + List list = itemService + .getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY); + assertThat(list.size(), equalTo(4)); + + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, one", null, 0, list.get(0)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, two", null, 1, list.get(1)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, three", null, 2, list.get(2)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "familyName, firstName", + "virtual::" + relationship.getID(), 3, list.get(3)); + assertThat(relationship.getLeftPlace(), equalTo(3)); + + + context.turnOffAuthorisationSystem(); + + MetadataValue metadataValueToUpdate = itemService.getMetadata(item, "dc", "contributor", "author", Item.ANY) + .get(1); + + // Switching the places of this relationship and metadata value to verify in the test later on that this + // updating works + metadataValueToUpdate.setPlace(3); + metadataValueService.update(context, metadataValueToUpdate); + relationship.setLeftPlace(1); + relationshipService.update(context, relationship); + + context.restoreAuthSystemState(); + + // Retrieve the list again and verify that the updating did indeed work + list = itemService.getMetadata(item, dcSchema, contributorElement, authorQualifier, Item.ANY); + + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, one", null, 0, list.get(0)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, two", null, 3, list.get(3)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "test, three", null, 2, list.get(2)); + assertMetadataValue(authorQualifier, contributorElement, dcSchema, "familyName, firstName", + "virtual::" + relationship.getID(), 1, list.get(1)); + assertThat(relationship.getLeftPlace(), equalTo(1)); + + + } + + private void assertMetadataValue(String authorQualifier, String contributorElement, String dcSchema, String value, + String authority, int place, MetadataValue metadataValue) { + assertThat(metadataValue.getValue(), equalTo(value)); + assertThat(metadataValue.getMetadataField().getMetadataSchema().getName(), equalTo(dcSchema)); + assertThat(metadataValue.getMetadataField().getElement(), equalTo(contributorElement)); + assertThat(metadataValue.getMetadataField().getQualifier(), equalTo(authorQualifier)); + assertThat(metadataValue.getAuthority(), equalTo(authority)); + assertThat(metadataValue.getPlace(), equalTo(place)); + } + + +} diff --git a/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplTest.java b/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplTest.java index 0a85c2160f..3c6f5d4485 100644 --- a/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplTest.java +++ b/dspace-api/src/test/java/org/dspace/content/RelationshipServiceImplTest.java @@ -114,11 +114,11 @@ public class RelationshipServiceImplTest { Item item = mock(Item.class); // Mock DAO to return mocked left place as 0 - when(relationshipDAO.findLeftPlaceByLeftItem(context, item)).thenReturn(0); + when(relationshipDAO.findNextLeftPlaceByLeftItem(context, item)).thenReturn(0); // The left place reported from out mocked item should match the DAO's report of the left place - assertEquals("TestFindLeftPlaceByLeftItem 0", relationshipDAO.findLeftPlaceByLeftItem(context, item), - relationshipService.findLeftPlaceByLeftItem(context, item)); + assertEquals("TestFindLeftPlaceByLeftItem 0", relationshipDAO.findNextLeftPlaceByLeftItem(context, item), + relationshipService.findNextLeftPlaceByLeftItem(context, item)); } @Test @@ -127,11 +127,11 @@ public class RelationshipServiceImplTest { Item item = mock(Item.class); // Mock lower level DAO to return mocked right place as 0 - when(relationshipDAO.findRightPlaceByRightItem(context, item)).thenReturn(0); + when(relationshipDAO.findNextRightPlaceByRightItem(context, item)).thenReturn(0); // The right place reported from out mocked item should match the DAO's report of the right place - assertEquals("TestFindRightPlaceByRightItem 0", relationshipDAO.findRightPlaceByRightItem(context, item), - relationshipService.findRightPlaceByRightItem(context, item)); + assertEquals("TestFindRightPlaceByRightItem 0", relationshipDAO.findNextRightPlaceByRightItem(context, item), + relationshipService.findNextRightPlaceByRightItem(context, item)); } @Test diff --git a/dspace-api/src/test/java/org/dspace/content/dao/RelationshipDAOImplTest.java b/dspace-api/src/test/java/org/dspace/content/dao/RelationshipDAOImplTest.java index cb73e3e06a..a8c882a377 100644 --- a/dspace-api/src/test/java/org/dspace/content/dao/RelationshipDAOImplTest.java +++ b/dspace-api/src/test/java/org/dspace/content/dao/RelationshipDAOImplTest.java @@ -138,24 +138,24 @@ public class RelationshipDAOImplTest extends AbstractIntegrationTest { } /** - * Test findLeftPlaceByLeftItem should return 0 given our test left Item itemOne. + * Test findNextLeftPlaceByLeftItem should return 0 given our test left Item itemOne. * * @throws Exception */ @Test - public void testFindLeftPlaceByLeftItem() throws Exception { - assertEquals("TestLeftPlaceByLeftItem 0", 0, relationshipService.findLeftPlaceByLeftItem(context, + public void testFindNextLeftPlaceByLeftItem() throws Exception { + assertEquals("TestNextLeftPlaceByLeftItem 0", 1, relationshipService.findNextLeftPlaceByLeftItem(context, itemOne)); } /** - * Test findRightPlaceByRightItem should return 0 given our test right Item itemTwo. + * Test findNextRightPlaceByRightItem should return 0 given our test right Item itemTwo. * * @throws Exception */ @Test - public void testFindRightPlaceByRightItem() throws Exception { - assertEquals("TestRightPlaceByRightItem 0", 0, relationshipService.findRightPlaceByRightItem(context, + public void testFindNextRightPlaceByRightItem() throws Exception { + assertEquals("TestNextRightPlaceByRightItem 0", 1, relationshipService.findNextRightPlaceByRightItem(context, itemTwo)); } @@ -183,4 +183,4 @@ public class RelationshipDAOImplTest extends AbstractIntegrationTest { } -} \ No newline at end of file +} diff --git a/dspace-api/src/test/java/org/dspace/scripts/DSpaceCommandLineParameterTest.java b/dspace-api/src/test/java/org/dspace/scripts/DSpaceCommandLineParameterTest.java new file mode 100644 index 0000000000..4e407d2e2e --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/scripts/DSpaceCommandLineParameterTest.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.scripts; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.LinkedList; +import java.util.List; + +import org.dspace.AbstractUnitTest; +import org.junit.Test; + +public class DSpaceCommandLineParameterTest extends AbstractUnitTest { + + @Test + public void constructorTest() { + String key = "-c"; + String value = "test"; + + DSpaceCommandLineParameter dSpaceCommandLineParameter = new DSpaceCommandLineParameter(key, value); + + assertThat("constructorTest 0", dSpaceCommandLineParameter.getName(), equalTo(key)); + assertThat("constructorTest 1", dSpaceCommandLineParameter.getValue(), equalTo(value)); + } + + @Test + public void constructorTestNullValue() { + String key = "-c"; + String value = null; + + DSpaceCommandLineParameter dSpaceCommandLineParameter = new DSpaceCommandLineParameter(key, value); + + assertThat("constructorTest 0", dSpaceCommandLineParameter.getName(), equalTo(key)); + assertThat("constructorTest 1", dSpaceCommandLineParameter.getValue(), equalTo(value)); + } + + @Test + public void singleParameterConstructorTest() { + String parameter = "-c test"; + + DSpaceCommandLineParameter dSpaceCommandLineParameter = new DSpaceCommandLineParameter(parameter); + + assertThat("singleParameterConstructorTest 0", dSpaceCommandLineParameter.getName(), equalTo("-c")); + assertThat("singleParameterConstructorTest 1", dSpaceCommandLineParameter.getValue(), equalTo("test")); + } + + @Test + public void singleParameterConstructorTestNoValue() { + String parameter = "-c"; + + DSpaceCommandLineParameter dSpaceCommandLineParameter = new DSpaceCommandLineParameter(parameter); + + assertThat("singleParameterConstructorTest 0", dSpaceCommandLineParameter.getName(), equalTo("-c")); + assertThat("singleParameterConstructorTest 1", dSpaceCommandLineParameter.getValue(), equalTo(null)); + } + + @Test + public void toStringTest() { + String key = "-c"; + String value = "test"; + + DSpaceCommandLineParameter dSpaceCommandLineParameter = new DSpaceCommandLineParameter(key, value); + + assertThat("toStringTest 0", dSpaceCommandLineParameter.getName(), equalTo(key)); + assertThat("toStringTest 1", dSpaceCommandLineParameter.getValue(), equalTo(value)); + + assertThat("toStringTest 2", dSpaceCommandLineParameter.toString(), equalTo("-c test")); + } + + @Test + public void toStringTestNullValue() { + String key = "-c"; + String value = null; + + DSpaceCommandLineParameter dSpaceCommandLineParameter = new DSpaceCommandLineParameter(key, value); + + assertThat("toStringTest 0", dSpaceCommandLineParameter.getName(), equalTo(key)); + assertThat("toStringTest 1", dSpaceCommandLineParameter.getValue(), equalTo(value)); + + assertThat("toStringTest 2", dSpaceCommandLineParameter.toString(), equalTo("-c")); + } + + @Test + public void equalsTest() { + String key = "-c"; + String value = "test"; + + DSpaceCommandLineParameter dSpaceCommandLineParameter = new DSpaceCommandLineParameter(key, value); + DSpaceCommandLineParameter dSpaceCommandLineParameter1 = new DSpaceCommandLineParameter(key, value); + + assertThat("toStringTest 0", dSpaceCommandLineParameter.getName(), equalTo(key)); + assertThat("toStringTest 1", dSpaceCommandLineParameter.getValue(), equalTo(value)); + + + assertThat("toStringTest 0", dSpaceCommandLineParameter1.getName(), equalTo(key)); + assertThat("toStringTest 1", dSpaceCommandLineParameter1.getValue(), equalTo(value)); + + assertTrue(dSpaceCommandLineParameter.equals(dSpaceCommandLineParameter1)); + } + + @Test + public void concatenateTest() { + String key = "-c"; + String value = "test"; + + DSpaceCommandLineParameter dSpaceCommandLineParameter = new DSpaceCommandLineParameter(key, value); + DSpaceCommandLineParameter dSpaceCommandLineParameter1 = new DSpaceCommandLineParameter(key, value); + + String key2 = "-r"; + String value2 = "testing"; + DSpaceCommandLineParameter dSpaceCommandLineParameter2 = new DSpaceCommandLineParameter(key2, value2); + + String key3 = "-t"; + String value3 = null; + DSpaceCommandLineParameter dSpaceCommandLineParameter3 = new DSpaceCommandLineParameter(key3, value3); + + List dSpaceCommandLineParameterList = new LinkedList<>(); + dSpaceCommandLineParameterList.add(dSpaceCommandLineParameter); + dSpaceCommandLineParameterList.add(dSpaceCommandLineParameter1); + dSpaceCommandLineParameterList.add(dSpaceCommandLineParameter2); + dSpaceCommandLineParameterList.add(dSpaceCommandLineParameter3); + String concatenedString = DSpaceCommandLineParameter.concatenate(dSpaceCommandLineParameterList); + + assertThat("concatenateTest", concatenedString, equalTo( + dSpaceCommandLineParameter.toString() + DSpaceCommandLineParameter.SEPARATOR + dSpaceCommandLineParameter1 + .toString() + DSpaceCommandLineParameter.SEPARATOR + dSpaceCommandLineParameter2 + .toString() + DSpaceCommandLineParameter.SEPARATOR + dSpaceCommandLineParameter3.toString())); + } +} diff --git a/dspace-api/src/test/java/org/dspace/scripts/impl/MockDSpaceRunnableScript.java b/dspace-api/src/test/java/org/dspace/scripts/impl/MockDSpaceRunnableScript.java new file mode 100644 index 0000000000..75f723d64b --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/scripts/impl/MockDSpaceRunnableScript.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.scripts.impl; + +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.dspace.scripts.DSpaceRunnable; + +public class MockDSpaceRunnableScript extends DSpaceRunnable { + + private MockDSpaceRunnableScript() { + Options options = constructOptions(); + this.options = options; + } + + @Override + public void internalRun() throws Exception { + } + + @Override + public void setup() throws ParseException { + if (!commandLine.hasOption("i")) { + throw new ParseException("-i is a mandatory parameter"); + } + } + + private Options constructOptions() { + Options options = new Options(); + + options.addOption("r", "remove", true, "description r"); + options.getOption("r").setType(String.class); + options.addOption("i", "index", true, "description i"); + options.getOption("i").setType(boolean.class); + options.getOption("i").setRequired(true); + return options; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java index 281494c081..6e282d8552 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/AuthenticationRestController.java @@ -11,6 +11,7 @@ import java.sql.SQLException; import java.util.Arrays; import javax.servlet.http.HttpServletRequest; +import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.converter.EPersonConverter; import org.dspace.app.rest.link.HalLinkService; import org.dspace.app.rest.model.AuthenticationStatusRest; @@ -18,6 +19,7 @@ import org.dspace.app.rest.model.AuthnRest; import org.dspace.app.rest.model.EPersonRest; import org.dspace.app.rest.model.hateoas.AuthenticationStatusResource; import org.dspace.app.rest.model.hateoas.AuthnResource; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.Utils; import org.dspace.core.Context; @@ -49,6 +51,9 @@ public class AuthenticationRestController implements InitializingBean { @Autowired DiscoverableEndpointsService discoverableEndpointsService; + @Autowired + private ConverterService converter; + @Autowired private EPersonConverter ePersonConverter; @@ -65,24 +70,24 @@ public class AuthenticationRestController implements InitializingBean { } @RequestMapping(method = RequestMethod.GET) - public AuthnResource authn() throws SQLException { - AuthnResource authnResource = new AuthnResource(new AuthnRest(), utils); - halLinkService.addLinks(authnResource); - return authnResource; + public AuthnResource authn() { + AuthnRest authnRest = new AuthnRest(); + authnRest.setProjection(utils.obtainProjection()); + return converter.toResource(authnRest); } @RequestMapping(value = "/status", method = RequestMethod.GET) public AuthenticationStatusResource status(HttpServletRequest request) throws SQLException { Context context = ContextUtil.obtainContext(request); EPersonRest ePersonRest = null; + Projection projection = utils.obtainProjection(); if (context.getCurrentUser() != null) { - ePersonRest = ePersonConverter.fromModelWithGroups(context, context.getCurrentUser()); + ePersonRest = ePersonConverter.fromModelWithGroups(context, context.getCurrentUser(), projection); } - AuthenticationStatusResource authenticationStatusResource = new AuthenticationStatusResource( - new AuthenticationStatusRest(ePersonRest), utils); - - halLinkService.addLinks(authenticationStatusResource); + AuthenticationStatusRest authenticationStatusRest = new AuthenticationStatusRest(ePersonRest); + authenticationStatusRest.setProjection(projection); + AuthenticationStatusResource authenticationStatusResource = converter.toResource(authenticationStatusRest); return authenticationStatusResource; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamBundleController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamBundleController.java new file mode 100644 index 0000000000..a28ec5a9a7 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamBundleController.java @@ -0,0 +1,153 @@ +/** + * 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.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID; +import static org.dspace.core.Constants.BUNDLE; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.link.HalLinkService; +import org.dspace.app.rest.model.BitstreamRest; +import org.dspace.app.rest.model.BundleRest; +import org.dspace.app.rest.model.hateoas.BundleResource; +import org.dspace.app.rest.repository.BitstreamRestRepository; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.DSpaceObject; +import org.dspace.content.service.BitstreamService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.ControllerUtils; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.hateoas.ResourceSupport; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PostAuthorize; +import org.springframework.security.access.prepost.PreAuthorize; +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; + + +/** + * Controller to access bitstreams and the bundles they're in + * Endpoint: /api/core/bitstreams/{uuid} + * This controller can: + * - request bundle a bitstream is in (GET /api/core/bitstreams/{uuid}/bundle) + * - move bitstreams between bundles (POST /api/core/bitstreams/{uuid}/bundle (text/uri-list) -d link-to-new-bundle) + * + */ +@RestController +@RequestMapping("/api/" + BitstreamRest.CATEGORY + "/" + BitstreamRest.PLURAL_NAME + + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID + "/" + BundleRest.NAME) +public class BitstreamBundleController { + + @Autowired + private BitstreamService bitstreamService; + + @Autowired + ConverterService converter; + + @Autowired + HalLinkService halLinkService; + + @Autowired + Utils utils; + + @Autowired + BitstreamRestRepository bitstreamRestRepository; + + /** + * This method gets the bundle of the bitstream that corresponds to to the provided bitstream uuid. When multiple + * bundles are present, only the first will be returned. + * + * @param uuid The UUID of the bitstream for which the bundle will be retrieved + * @param response The response object + * @param request The request object + * @return The wrapped resource containing the first bundle of the bitstream + * @throws IOException + * @throws SQLException + * @throws AuthorizeException + */ + @PreAuthorize("hasPermission(#uuid, 'BITSTREAM', 'READ')") + @RequestMapping(method = {RequestMethod.GET, RequestMethod.HEAD}) + public ResponseEntity getBundle(@PathVariable UUID uuid, HttpServletResponse response, + HttpServletRequest request) + throws IOException, SQLException, AuthorizeException { + + Context context = ContextUtil.obtainContext(request); + + Bitstream bitstream = bitstreamService.find(context, uuid); + if (bitstream == null) { + throw new ResourceNotFoundException( + BitstreamRest.CATEGORY + "." + BitstreamRest.NAME + " with id: " + uuid + " not found"); + } + + List bundles = bitstream.getBundles(); + + if (bundles.isEmpty()) { + return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); + } + + BundleResource bundleResource = converter.toResource( + converter.toRest(bundles.get(0), utils.obtainProjection())); + + return ControllerUtils.toResponseEntity(HttpStatus.OK, null, bundleResource); + } + + /** + * This method moves the bitstream to the bundle corresponding the the link provided in the body of the put request + * + * @param uuid The UUID of the bitstream for which the bundle will be retrieved + * @param response The response object + * @param request The request object + * @return The wrapped resource containing the new bundle of the bitstream + * @throws SQLException + * @throws IOException + * @throws AuthorizeException + */ + @RequestMapping(method = RequestMethod.PUT, consumes = {"text/uri-list"}) + @PreAuthorize("hasPermission(#uuid, 'BITSTREAM','WRITE')") + @PostAuthorize("returnObject != null") + public BundleRest move(@PathVariable UUID uuid, HttpServletResponse response, + HttpServletRequest request) + throws SQLException, IOException, AuthorizeException { + Context context = ContextUtil.obtainContext(request); + + List dsoList = utils.constructDSpaceObjectList(context, utils.getStringListFromRequest(request)); + + if (dsoList.size() != 1 || dsoList.get(0).getType() != BUNDLE) { + throw new UnprocessableEntityException("No bundle has been specified " + + "or the data cannot be resolved to a bundle."); + } + + Bitstream bitstream = bitstreamService.find(context, uuid); + if (bitstream == null) { + throw new ResourceNotFoundException("Bitstream with id: " + uuid + " not found"); + } + + BundleRest bundleRest = bitstreamRestRepository.performBitstreamMove(context, bitstream, + (Bundle) dsoList.get(0)); + + context.commit(); + + return bundleRest; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java index 868e6413f2..1ecd7a33ff 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BitstreamRestController.java @@ -8,6 +8,7 @@ package org.dspace.app.rest; import static org.dspace.app.rest.utils.ContextUtil.obtainContext; +import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID; import static org.springframework.web.bind.annotation.RequestMethod.PUT; import java.io.IOException; @@ -22,10 +23,11 @@ import javax.ws.rs.core.Response; import org.apache.catalina.connector.ClientAbortException; import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.Logger; -import org.dspace.app.rest.converter.BitstreamConverter; +import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.model.hateoas.BitstreamResource; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.MultipartFileSender; import org.dspace.app.rest.utils.Utils; @@ -65,7 +67,7 @@ import org.springframework.web.bind.annotation.RestController; */ @RestController @RequestMapping("/api/" + BitstreamRest.CATEGORY + "/" + BitstreamRest.PLURAL_NAME - + "/{uuid:[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}}") + + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID) public class BitstreamRestController { private static final Logger log = org.apache.logging.log4j.LogManager @@ -90,7 +92,7 @@ public class BitstreamRestController { private ConfigurationService configurationService; @Autowired - BitstreamConverter converter; + ConverterService converter; @Autowired Utils utils; @@ -238,7 +240,7 @@ public class BitstreamRestController { context.commit(); - return (BitstreamResource) utils.getResourceRepository(BitstreamRest.CATEGORY, BitstreamRest.NAME) - .wrapResource(converter.fromModel(context.reloadEntity(bitstream))); + BitstreamRest bitstreamRest = converter.toRest(context.reloadEntity(bitstream), Projection.DEFAULT); + return converter.toResource(bitstreamRest); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/BundleUploadBitstreamController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BundleUploadBitstreamController.java new file mode 100644 index 0000000000..d7c0109e0c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/BundleUploadBitstreamController.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.rest; + +import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.SQLException; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.BitstreamRest; +import org.dspace.app.rest.model.BundleRest; +import org.dspace.app.rest.model.hateoas.BitstreamResource; +import org.dspace.app.rest.repository.BundleRestRepository; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.rest.utils.Utils; +import org.dspace.content.Bundle; +import org.dspace.content.service.BundleService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.ControllerUtils; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.hateoas.ResourceSupport; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +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; +import org.springframework.web.multipart.MultipartFile; + +/** + * Controller to upload bitstreams to a certain bundle, indicated by a uuid in the request + * Usage: POST /api/core/bundles/{uuid}/bitstreams (with file and properties of file in request) + * Example: + *
+ * {@code
+ * curl https:///api/core/bundles/d3599177-0408-403b-9f8d-d300edd79edb/bitstreams
+ *  -XPOST -H 'Content-Type: multipart/form-data' \
+ *  -H 'Authorization: Bearer eyJhbGciOiJI...' \
+ *  -F "file=@Downloads/test.html" \
+ *  -F 'properties={ "name": "test.html", "metadata": { "dc.description": [ { "value": "example file", "language": null,
+ *          "authority": null, "confidence": -1, "place": 0 } ]}, "bundleName": "ORIGINAL" };type=application/json'
+ * }
+ * 
+ */ +@RestController +@RequestMapping("/api/" + BundleRest.CATEGORY + "/" + BundleRest.PLURAL_NAME + "/" + + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID + "/" + BitstreamRest.PLURAL_NAME) +public class BundleUploadBitstreamController { + + private static final Logger log = LogManager.getLogger(); + + @Autowired + protected Utils utils; + + @Autowired + private BundleService bundleService; + + @Autowired + private BundleRestRepository bundleRestRepository; + + @Autowired + private ConverterService converter; + + /** + * Method to upload a Bitstream to a Bundle with the given UUID in the URL. This will create a Bitstream with the + * file provided in the request and attach this to the Item that matches the UUID in the URL. + * This will only work for uploading one file, any extra files will silently be ignored + * + * @return The created BitstreamResource + */ + @RequestMapping(method = RequestMethod.POST, headers = "content-type=multipart/form-data") + @PreAuthorize("hasPermission(#uuid, 'BUNDLE', 'ADD') && hasPermission(#uuid, 'BUNDLE', 'WRITE')") + public ResponseEntity uploadBitstream(HttpServletRequest request, @PathVariable UUID uuid, + @RequestParam("file") MultipartFile uploadfile, + @RequestParam(value = "properties", required = false) String properties) { + + Context context = ContextUtil.obtainContext(request); + Bundle bundle = null; + try { + bundle = bundleService.find(context, uuid); + } catch (SQLException e) { + log.error("Something went wrong trying to find the Bundle with uuid: " + uuid, e); + } + if (bundle == null) { + throw new ResourceNotFoundException("The given uuid did not resolve to a Bundle on the server: " + uuid); + } + InputStream fileInputStream = null; + try { + fileInputStream = uploadfile.getInputStream(); + } catch (IOException e) { + log.error("Something went wrong when trying to read the inputstream from the given file in the request", + e); + throw new UnprocessableEntityException("The InputStream from the file couldn't be read", e); + } + + BitstreamRest bitstreamRest = bundleRestRepository.uploadBitstream( + context, bundle, uploadfile.getOriginalFilename(), fileInputStream, properties); + BitstreamResource bitstreamResource = converter.toResource(bitstreamRest); + + return ControllerUtils.toResponseEntity(HttpStatus.CREATED, null, bitstreamResource); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionHarvestSettingsController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionHarvestSettingsController.java index 274c393d9b..856804808c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionHarvestSettingsController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/CollectionHarvestSettingsController.java @@ -12,6 +12,7 @@ import java.util.UUID; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.converter.HarvestedCollectionConverter; import org.dspace.app.rest.link.HalLinkService; import org.dspace.app.rest.model.HarvestedCollectionRest; @@ -48,6 +49,9 @@ public class CollectionHarvestSettingsController { @Autowired CollectionService collectionService; + @Autowired + ConverterService converter; + @Autowired HarvestedCollectionService harvestedCollectionService; @@ -80,9 +84,7 @@ public class CollectionHarvestSettingsController { } HarvestedCollectionRest harvestedCollectionRest = harvestedCollectionRestRepository.findOne(collection); - HarvestedCollectionResource resource = new HarvestedCollectionResource(harvestedCollectionRest); - - halLinkService.addLinks(resource); + HarvestedCollectionResource resource = converter.toResource(harvestedCollectionRest); return resource; } @@ -114,8 +116,7 @@ public class CollectionHarvestSettingsController { // Return a harvestedCollectionResource only if a new harvestedCollection was created if (harvestedCollectionRest != null) { - harvestedCollectionResource = new HarvestedCollectionResource(harvestedCollectionRest); - halLinkService.addLinks(harvestedCollectionResource); + harvestedCollectionResource = converter.toResource(harvestedCollectionRest); } context.commit(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoveryRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoveryRestController.java index 6b37d40fa7..6be17ae24c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoveryRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/DiscoveryRestController.java @@ -14,6 +14,7 @@ import java.util.Objects; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.link.HalLinkService; import org.dspace.app.rest.model.FacetConfigurationRest; import org.dspace.app.rest.model.FacetResultsRest; @@ -61,6 +62,9 @@ public class DiscoveryRestController implements InitializingBean { @Autowired private HalLinkService halLinkService; + @Autowired + private ConverterService converter; + @Override public void afterPropertiesSet() throws Exception { discoverableEndpointsService @@ -74,8 +78,7 @@ public class DiscoveryRestController implements InitializingBean { throws Exception { SearchSupportRest searchSupportRest = discoveryRestRepository.getSearchSupport(); - SearchSupportResource searchSupportResource = new SearchSupportResource(searchSupportRest); - halLinkService.addLinks(searchSupportResource); + SearchSupportResource searchSupportResource = converter.toResource(searchSupportRest); return searchSupportResource; } @@ -91,9 +94,7 @@ public class DiscoveryRestController implements InitializingBean { SearchConfigurationRest searchConfigurationRest = discoveryRestRepository .getSearchConfiguration(dsoScope, configuration); - SearchConfigurationResource searchConfigurationResource = new SearchConfigurationResource( - searchConfigurationRest); - halLinkService.addLinks(searchConfigurationResource); + SearchConfigurationResource searchConfigurationResource = converter.toResource(searchConfigurationRest); return searchConfigurationResource; } @@ -142,9 +143,8 @@ public class DiscoveryRestController implements InitializingBean { } //Get the Search results in JSON format - SearchResultsRest searchResultsRest = null; - searchResultsRest = discoveryRestRepository - .getSearchObjects(query, dsoType, dsoScope, configuration, searchFilters, page); + SearchResultsRest searchResultsRest = discoveryRestRepository + .getSearchObjects(query, dsoType, dsoScope, configuration, searchFilters, page, utils.obtainProjection()); //Convert the Search JSON results to paginated HAL resources SearchResultsResource searchResultsResource = new SearchResultsResource(searchResultsRest, utils, page); @@ -164,7 +164,7 @@ public class DiscoveryRestController implements InitializingBean { FacetConfigurationRest facetConfigurationRest = discoveryRestRepository .getFacetsConfiguration(dsoScope, configuration); - FacetConfigurationResource facetConfigurationResource = new FacetConfigurationResource(facetConfigurationRest); + FacetConfigurationResource facetConfigurationResource = converter.toResource(facetConfigurationRest); halLinkService.addLinks(facetConfigurationResource, pageable); return facetConfigurationResource; @@ -192,7 +192,7 @@ public class DiscoveryRestController implements InitializingBean { FacetResultsRest facetResultsRest = discoveryRestRepository .getFacetObjects(facetName, prefix, query, dsoType, dsoScope, configuration, searchFilters, page); - FacetResultsResource facetResultsResource = new FacetResultsResource(facetResultsRest); + FacetResultsResource facetResultsResource = converter.toResource(facetResultsRest); halLinkService.addLinks(facetResultsResource, page); return facetResultsResource; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/HarvesterMetadataController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/HarvesterMetadataController.java index 121f886fff..dad1a2eb75 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/HarvesterMetadataController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/HarvesterMetadataController.java @@ -12,12 +12,12 @@ import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.link.HalLinkService; import org.dspace.app.rest.model.HarvesterMetadataRest; import org.dspace.app.rest.model.hateoas.HarvesterMetadataResource; import org.dspace.app.rest.utils.Utils; import org.dspace.harvest.OAIHarvester; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -38,6 +38,9 @@ public class HarvesterMetadataController { @Autowired private HalLinkService halLinkService; + @Autowired + private ConverterService converter; + /** * GET endpoint that returns all available metadata formats * @param request The request object @@ -46,15 +49,14 @@ public class HarvesterMetadataController { */ @RequestMapping(method = RequestMethod.GET) public HarvesterMetadataResource get(HttpServletRequest request, - HttpServletResponse response) { + HttpServletResponse response) { List> configs = OAIHarvester.getAvailableMetadataFormats(); HarvesterMetadataRest data = new HarvesterMetadataRest(); + data.setProjection(utils.obtainProjection()); data.setConfigs(configs); - HarvesterMetadataResource resource = new HarvesterMetadataResource(data, utils); - halLinkService.addLinks(resource); - + HarvesterMetadataResource resource = converter.toResource(data); return resource; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/IdentifierRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/IdentifierRestController.java index 3c7570fac7..09b6468b3c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/IdentifierRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/IdentifierRestController.java @@ -13,15 +13,15 @@ import java.io.IOException; import java.net.URI; import java.sql.SQLException; import java.util.Arrays; - import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger; import org.atteo.evo.inflector.English; -import org.dspace.app.rest.converter.GenericDSpaceObjectConverter; +import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.model.DSpaceObjectRest; import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.rest.utils.Utils; import org.dspace.content.DSpaceObject; import org.dspace.core.Context; import org.dspace.identifier.IdentifierNotFoundException; @@ -53,7 +53,10 @@ public class IdentifierRestController implements InitializingBean { Logger.getLogger(IdentifierRestController.class); @Autowired - private GenericDSpaceObjectConverter converter; + private ConverterService converter; + + @Autowired + private Utils utils; @Autowired private DiscoverableEndpointsService discoverableEndpointsService; @@ -84,7 +87,7 @@ public class IdentifierRestController implements InitializingBean { try { dso = identifierService.resolve(context, id); if (dso != null) { - DSpaceObjectRest dsor = converter.convert(dso); + DSpaceObjectRest dsor = converter.toRest(dso, utils.obtainProjection()); URI link = linkTo(dsor.getController(), dsor.getCategory(), English.plural(dsor.getType())) .slash(dsor.getId()).toUri(); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemAddBundleController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemAddBundleController.java new file mode 100644 index 0000000000..8354e0048b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemAddBundleController.java @@ -0,0 +1,114 @@ +/** + * 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.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.converter.MetadataConverter; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.BundleRest; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.model.hateoas.BundleResource; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.repository.ItemRestRepository; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.content.service.ItemService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.ControllerUtils; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.hateoas.ResourceSupport; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +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; + +/** + * Controller to add bundles to a certain item, indicated by a uuid in the request + * Usage: POST /api/core/items/<:uuid>/bundles (with name and metadata of bundle in request json) + * Example: + *
+ * {@code
+ * curl -X POST https:///api/core/items/1911e8a4-6939-490c-b58b-a5d70f8d91fb/bundles
+ *  -H 'Authorization: Bearer eyJhbGciOiJI...'
+ *  -H 'Content-Type: application/json
+ *  -d {
+ *      "name": "ORIGINAL",
+ *      "metadata": {...}
+ *     }
+ * }
+ * 
+ */ +@RestController +@RequestMapping("/api/" + ItemRest.CATEGORY + "/" + ItemRest.PLURAL_NAME + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID + + "/" + BundleRest.PLURAL_NAME) +public class ItemAddBundleController { + + @Autowired + ConverterService converter; + + @Autowired + ItemService itemService; + + @Autowired + ItemRestRepository itemRestRepository; + + @Autowired + MetadataConverter metadataConverter; + + @Autowired + Utils utils; + + /** + * Method to add a Bundle to an Item with the given UUID in the URL. This will create a Bundle with the + * name provided in the request and attach this to the Item that matches the UUID in the URL. + * + * @return The created BundleResource + */ + @RequestMapping(method = RequestMethod.POST) + @PreAuthorize("hasPermission(#uuid, 'ITEM', 'ADD')") + public ResponseEntity addBundleToItem(@PathVariable UUID uuid, + HttpServletRequest request, + HttpServletResponse response) + throws SQLException, AuthorizeException { + Context context = ContextUtil.obtainContext(request); + + Item item = itemService.find(context, uuid); + + if (item == null) { + throw new ResourceNotFoundException("Could not find item with id " + uuid); + } + + BundleRest bundleRest; + try { + bundleRest = new ObjectMapper().readValue(request.getInputStream(), BundleRest.class); + } catch (IOException excIO) { + throw new UnprocessableEntityException("Could not parse request body"); + } + + Bundle bundle = itemRestRepository.addBundleToItem(context, item, bundleRest); + BundleResource bundleResource = converter.toResource(converter.toRest(bundle, Projection.DEFAULT)); + return ControllerUtils.toResponseEntity(HttpStatus.CREATED, null, bundleResource); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestController.java index c5ccca34f0..67aca95c10 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemOwningCollectionUpdateRestController.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest; +import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID; import static org.dspace.core.Constants.COLLECTION; import java.io.IOException; @@ -16,10 +17,11 @@ import java.util.UUID; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.dspace.app.rest.converter.CollectionConverter; +import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.CollectionRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.AuthorizeException; @@ -41,13 +43,11 @@ 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/code/items/{itemUuid}/owningCollection endpoint - * where the itemUuid corresponds to the item of which you want to edit the owning collection. + * This controller will handle all the incoming calls on the api/code/items/{uuid}/owningCollection endpoint + * where the uuid corresponds to the item of which you want to edit the owning collection. */ @RestController -@RequestMapping("/api/core/items/" + - "{itemUuid:[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" + - "}}/owningCollection") +@RequestMapping("/api/core/items" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID + "/owningCollection") public class ItemOwningCollectionUpdateRestController { @Autowired @@ -60,7 +60,7 @@ public class ItemOwningCollectionUpdateRestController { AuthorizeService authorizeService; @Autowired - CollectionConverter converter; + ConverterService converter; @Autowired Utils utils; @@ -69,7 +69,7 @@ public class ItemOwningCollectionUpdateRestController { * This method will update the owning collection of the item that correspond to the provided item uuid, effectively * moving the item to the new collection. * - * @param itemUuid The UUID of the item that will be moved + * @param uuid The UUID of the item that will be moved * @param response The response object * @param request The request object * @return The wrapped resource containing the new owning collection or null when the item was not moved @@ -78,9 +78,9 @@ public class ItemOwningCollectionUpdateRestController { * @throws AuthorizeException If the user is not authorized to perform the move action */ @RequestMapping(method = RequestMethod.PUT, consumes = {"text/uri-list"}) - @PreAuthorize("hasPermission(#itemUuid, 'ITEM','WRITE')") + @PreAuthorize("hasPermission(#uuid, 'ITEM','WRITE')") @PostAuthorize("returnObject != null") - public CollectionRest move(@PathVariable UUID itemUuid, HttpServletResponse response, + public CollectionRest move(@PathVariable UUID uuid, HttpServletResponse response, HttpServletRequest request) throws SQLException, IOException, AuthorizeException { Context context = ContextUtil.obtainContext(request); @@ -92,12 +92,12 @@ public class ItemOwningCollectionUpdateRestController { "or the data cannot be resolved to a collection."); } - Collection targetCollection = performItemMove(context, itemUuid, (Collection) dsoList.get(0)); + Collection targetCollection = performItemMove(context, uuid, (Collection) dsoList.get(0)); if (targetCollection == null) { return null; } - return converter.fromModel(targetCollection); + return converter.toRest(targetCollection, Projection.DEFAULT); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemUploadController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemUploadController.java deleted file mode 100644 index f119e8e102..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ItemUploadController.java +++ /dev/null @@ -1,163 +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.app.rest; - -import java.io.IOException; -import java.io.InputStream; -import java.sql.SQLException; -import java.util.UUID; -import javax.servlet.http.HttpServletRequest; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.dspace.app.rest.converter.BitstreamConverter; -import org.dspace.app.rest.converter.MetadataConverter; -import org.dspace.app.rest.exception.UnprocessableEntityException; -import org.dspace.app.rest.model.BitstreamRest; -import org.dspace.app.rest.model.hateoas.BitstreamResource; -import org.dspace.app.rest.utils.ContextUtil; -import org.dspace.app.rest.utils.Utils; -import org.dspace.authorize.AuthorizeException; -import org.dspace.content.Bitstream; -import org.dspace.content.BitstreamFormat; -import org.dspace.content.Item; -import org.dspace.content.service.BitstreamFormatService; -import org.dspace.content.service.BitstreamService; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.rest.webmvc.ResourceNotFoundException; -import org.springframework.security.access.prepost.PreAuthorize; -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; -import org.springframework.web.multipart.MultipartFile; - -@RestController -@RequestMapping("/api/core/items/{uuid}") -public class ItemUploadController { - - private static final Logger log = LogManager.getLogger(); - - @Autowired - protected Utils utils; - - @Autowired - private ItemService itemService; - - @Autowired - private BitstreamService bitstreamService; - - @Autowired - private BitstreamConverter bitstreamConverter; - - @Autowired - private MetadataConverter metadataConverter; - - @Autowired - private BitstreamFormatService bitstreamFormatService; - - /** - * Method to upload a Bitstream to an Item with the given UUID in the URL. This will create a Bitstream with the - * file provided in the request and attach this to the Item that matches the UUID in the URL. - * This will only work for uploading one file, any extra files will silently be ignored - * @return The created BitstreamResource - */ - @RequestMapping(method = RequestMethod.POST, value = "/bitstreams", headers = "content-type=multipart/form-data") - @PreAuthorize("hasPermission(#uuid, 'ITEM', 'WRITE') && hasPermission(#uuid, 'ITEM', 'ADD')") - public BitstreamResource uploadBitstream(HttpServletRequest request, @PathVariable UUID uuid, - @RequestParam("file") MultipartFile uploadfile, - @RequestParam(value = "properties", required = false) String properties) { - - Context context = ContextUtil.obtainContext(request); - Item item = null; - Bitstream bitstream = null; - try { - item = itemService.find(context, uuid); - } catch (SQLException e) { - log.error("Something went wrong trying to find the Item with uuid: " + uuid, e); - } - if (item == null) { - throw new ResourceNotFoundException("The given uuid did not resolve to an Item on the server: " + uuid); - } - InputStream fileInputStream = null; - try { - fileInputStream = uploadfile.getInputStream(); - } catch (IOException e) { - log.error("Something went wrong when trying to read the inputstream from the given file in the request", - e); - throw new UnprocessableEntityException("The InputStream from the file couldn't be read", e); - } - try { - bitstream = processBitstreamCreation(context, item, fileInputStream, properties, - uploadfile.getOriginalFilename()); - itemService.update(context, item); - context.commit(); - } catch (AuthorizeException | IOException | SQLException e) { - String message = "Something went wrong with trying to create the single bitstream for file with filename: " - + uploadfile.getOriginalFilename() - + " for item with uuid: " + uuid + " and possible properties: " + properties; - log.error(message, e); - throw new RuntimeException(message, e); - } - return new BitstreamResource(bitstreamConverter.fromModel(bitstream), utils); - } - - /** - * Creates the bitstream based on the given parameters - * @param context The context - * @param item The item where the bitstream should be store - * @param fileInputStream The input stream used to create the bitstream - * @param properties The properties to be assigned to the bitstream - * @param originalFilename The filename as it was uploaded - * @return The bitstream which has been created - */ - private Bitstream processBitstreamCreation(Context context, Item item, InputStream fileInputStream, - String properties, String originalFilename) - throws AuthorizeException, IOException, SQLException { - - Bitstream bitstream = null; - if (StringUtils.isNotBlank(properties)) { - ObjectMapper mapper = new ObjectMapper(); - BitstreamRest bitstreamRest = null; - try { - bitstreamRest = mapper.readValue(properties, BitstreamRest.class); - } catch (Exception e) { - throw new UnprocessableEntityException("The properties parameter was incorrect: " + properties); - } - String bundleName = bitstreamRest.getBundleName(); - if (StringUtils.isBlank(bundleName)) { - throw new UnprocessableEntityException("Properties without a bundleName is not allowed"); - } - bitstream = itemService.createSingleBitstream(context, fileInputStream, item, bundleName); - if (bitstreamRest.getMetadata() != null) { - metadataConverter.setMetadata(context, bitstream, bitstreamRest.getMetadata()); - } - String name = bitstreamRest.getName(); - if (StringUtils.isNotBlank(name)) { - bitstream.setName(context, name); - } else { - bitstream.setName(context, originalFilename); - } - - } else { - bitstream = itemService.createSingleBitstream(context, fileInputStream, item); - bitstream.setName(context, originalFilename); - - } - BitstreamFormat bitstreamFormat = bitstreamFormatService.guessFormat(context, bitstream); - bitstreamService.setFormat(context, bitstream, bitstreamFormat); - bitstreamService.update(context, bitstream); - - return bitstream; - } -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/MappedCollectionRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/MappedCollectionRestController.java index 791e85a59b..16d9429882 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/MappedCollectionRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/MappedCollectionRestController.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest; +import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID; import static org.dspace.core.Constants.COLLECTION; import java.io.IOException; @@ -18,7 +19,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger; -import org.dspace.app.rest.converter.CollectionConverter; +import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.MethodNotAllowedException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.link.HalLinkService; @@ -46,9 +47,7 @@ import org.springframework.web.bind.annotation.RestController; * This class will typically receive a UUID that resolves to an Item and it'll perform logic on its collections */ @RestController -@RequestMapping("/api/core/items/" + - "{uuid:[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}}" + - "/mappedCollections") +@RequestMapping("/api/core/items" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID + "/mappedCollections") public class MappedCollectionRestController { private static final Logger log = Logger.getLogger(MappedCollectionRestController.class); @@ -57,7 +56,7 @@ public class MappedCollectionRestController { private ItemService itemService; @Autowired - private CollectionConverter collectionConverter; + private ConverterService converter; @Autowired private CollectionService collectionService; @@ -100,18 +99,16 @@ public class MappedCollectionRestController { List mappingCollectionRest = new LinkedList<>(); for (Collection collection : collections) { if (collection.getID() != owningCollectionUuid) { - mappingCollectionRest.add(collectionConverter.fromModel(collection)); + mappingCollectionRest.add(converter.toRest(collection, utils.obtainProjection())); } } MappedCollectionRestWrapper mappingCollectionRestWrapper = new MappedCollectionRestWrapper(); + mappingCollectionRestWrapper.setProjection(utils.obtainProjection()); mappingCollectionRestWrapper.setMappedCollectionRestList(mappingCollectionRest); mappingCollectionRestWrapper.setItem(item); - MappedCollectionResourceWrapper mappingCollectionResourceWrapper = new MappedCollectionResourceWrapper( - mappingCollectionRestWrapper, utils, pageable); - - - halLinkService.addLinks(mappingCollectionResourceWrapper); + MappedCollectionResourceWrapper mappingCollectionResourceWrapper = + converter.toResource(mappingCollectionRestWrapper); return mappingCollectionResourceWrapper; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/MappedItemRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/MappedItemRestController.java index 984f5c7ad7..be450dd9cd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/MappedItemRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/MappedItemRestController.java @@ -7,6 +7,8 @@ */ package org.dspace.app.rest; +import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID; + import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -15,7 +17,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger; -import org.dspace.app.rest.converter.ItemConverter; +import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.link.HalLinkService; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.model.MappedItemRestWrapper; @@ -40,8 +42,7 @@ import org.springframework.web.bind.annotation.RestController; * have the given collection as their owning collection */ @RestController -@RequestMapping("/api/core/collections/" + - "{uuid:[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}}/mappedItems") +@RequestMapping("/api/core/collections" + REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID + "/mappedItems") public class MappedItemRestController { private static final Logger log = Logger.getLogger(MappedItemRestController.class); @@ -53,7 +54,7 @@ public class MappedItemRestController { private ItemService itemService; @Autowired - private ItemConverter itemConverter; + private ConverterService converter; @Autowired Utils utils; @@ -95,11 +96,12 @@ public class MappedItemRestController { while (itemIterator.hasNext()) { Item item = itemIterator.next(); if (item.getOwningCollection().getID() != uuid) { - mappedItemRestList.add(itemConverter.fromModel(item)); + mappedItemRestList.add(converter.toRest(item, utils.obtainProjection())); } } MappedItemRestWrapper mappedItemRestWrapper = new MappedItemRestWrapper(); + mappedItemRestWrapper.setProjection(utils.obtainProjection()); mappedItemRestWrapper.setMappedItemRestList(mappedItemRestList); mappedItemRestWrapper.setCollectionUuid(uuid); MappedItemResourceWrapper mappedItemResourceWrapper = diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RelationshipRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RelationshipRestController.java index 287eb0b2eb..4959d5ac75 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RelationshipRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RelationshipRestController.java @@ -7,6 +7,8 @@ */ package org.dspace.app.rest; +import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_DIGIT; + import java.sql.SQLException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -29,11 +31,6 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("/api/core/relationships") public class RelationshipRestController { - /** - * Regular expression in the request mapping to accept number as identifier - */ - private static final String REGEX_REQUESTMAPPING_IDENTIFIER_AS_DIGIT = "/{id:\\d+}"; - @Autowired private RelationshipRestRepository relationshipRestRepository; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RelationshipTypeRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RelationshipTypeRestController.java index 303fe6c7e9..490eefb6c4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RelationshipTypeRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RelationshipTypeRestController.java @@ -13,11 +13,12 @@ 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.converter.ConverterService; 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.projection.Projection; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.Utils; import org.dspace.content.EntityType; @@ -48,7 +49,7 @@ public class RelationshipTypeRestController { private EntityTypeService entityTypeService; @Autowired - private RelationshipTypeConverter relationshipTypeConverter; + private ConverterService converter; @Autowired private Utils utils; @@ -67,7 +68,8 @@ public class RelationshipTypeRestController { * @throws SQLException If something goes wrong */ @RequestMapping(method = RequestMethod.GET) - public RelationshipTypeResourceWrapper retrieve(@PathVariable Integer id, HttpServletResponse response, + public RelationshipTypeResourceWrapper retrieve(@PathVariable Integer id, + HttpServletResponse response, HttpServletRequest request) throws SQLException { Context context = ContextUtil.obtainContext(request); EntityType entityType = entityTypeService.find(context, id); @@ -75,19 +77,19 @@ public class RelationshipTypeRestController { List relationshipTypeRests = new LinkedList<>(); + Projection projection = utils.obtainProjection(); for (RelationshipType relationshipType : list) { - relationshipTypeRests.add(relationshipTypeConverter.fromModel(relationshipType)); + relationshipTypeRests.add(converter.toRest(relationshipType, projection)); } - RelationshipTypeRestWrapper relationshipTypeRestWrapper = new RelationshipTypeRestWrapper(); + relationshipTypeRestWrapper.setProjection(projection); relationshipTypeRestWrapper.setEntityTypeId(id); relationshipTypeRestWrapper.setEntityTypeLabel(entityType.getLabel()); relationshipTypeRestWrapper.setRelationshipTypeRestList(relationshipTypeRests); - RelationshipTypeResourceWrapper relationshipTypeResourceWrapper = new RelationshipTypeResourceWrapper( - relationshipTypeRestWrapper, utils); - halLinkService.addLinks(relationshipTypeResourceWrapper); + RelationshipTypeResourceWrapper relationshipTypeResourceWrapper = + converter.toResource(relationshipTypeRestWrapper); return relationshipTypeResourceWrapper; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java index 15349aec9c..d4cb82dcc4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RestResourceController.java @@ -7,6 +7,9 @@ */ package org.dspace.app.rest; +import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_DIGIT; +import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG; +import static org.dspace.app.rest.utils.RegexUtils.REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; @@ -19,17 +22,16 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.UUID; - import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; - import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.atteo.evo.inflector.English; +import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.converter.JsonPatchConverter; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.PaginationException; @@ -64,7 +66,6 @@ import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.data.web.PagedResourcesAssembler; import org.springframework.hateoas.Link; import org.springframework.hateoas.PagedResources; -import org.springframework.hateoas.Resource; import org.springframework.hateoas.ResourceSupport; import org.springframework.hateoas.Resources; import org.springframework.hateoas.UriTemplate; @@ -95,24 +96,6 @@ import org.springframework.web.multipart.MultipartFile; @SuppressWarnings("rawtypes") public class RestResourceController implements InitializingBean { - /** - * Regular expression in the request mapping to accept UUID as identifier - */ - private static final String REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID = - "/{uuid:[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}}"; - - /** - * 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_IDENTIFIER_AS_STRING_VERSION_STRONG = "/{id:^(?!^\\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+\\-]+$+}"; - - /** - * Regular expression in the request mapping to accept number as identifier - */ - private static final String REGEX_REQUESTMAPPING_IDENTIFIER_AS_DIGIT = "/{id:\\d+}"; - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(RestResourceController.class); @Autowired @@ -130,6 +113,9 @@ public class RestResourceController implements InitializingBean { @Autowired HalLinkService linkService; + @Autowired + ConverterService converter; + @Override public void afterPropertiesSet() { List links = new ArrayList(); @@ -152,22 +138,20 @@ public class RestResourceController implements InitializingBean { * * Note that the regular expression in the request mapping accept a number as identifier; * - * Please see {@link RestResourceController#findOne(String, String, String, String)} for findOne with string as + * Please see {@link RestResourceController#findOne(String, String, String)} for findOne with string as * identifier - * and see {@link RestResourceController#findOne(String, String, UUID, String)} for uuid as identifier + * and see {@link RestResourceController#findOne(String, String, UUID)} for uuid as identifier * * @param apiCategory * @param model * @param id - * @param projection * @return */ @RequestMapping(method = RequestMethod.GET, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_DIGIT) @SuppressWarnings("unchecked") public DSpaceResource findOne(@PathVariable String apiCategory, @PathVariable String model, - @PathVariable Integer id, - @RequestParam(required = false) String projection) { - return findOneInternal(apiCategory, model, id, projection); + @PathVariable Integer id) { + return findOneInternal(apiCategory, model, id); } /** @@ -186,22 +170,20 @@ public class RestResourceController implements InitializingBean { * * * - * Please see {@link RestResourceController#findOne(String, String, Integer, String)} for findOne with number as + * Please see {@link RestResourceController#findOne(String, String, Integer)} for findOne with number as * identifier - * and see {@link RestResourceController#findOne(String, String, UUID, String)} for uuid as identifier + * and see {@link RestResourceController#findOne(String, String, UUID)} for uuid as identifier * * @param apiCategory * @param model * @param id - * @param projection * @return */ @RequestMapping(method = RequestMethod.GET, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG) @SuppressWarnings("unchecked") public DSpaceResource findOne(@PathVariable String apiCategory, @PathVariable String model, - @PathVariable String id, - @RequestParam(required = false) String projection) { - return findOneInternal(apiCategory, model, id, projection); + @PathVariable String id) { + return findOneInternal(apiCategory, model, id); } /** @@ -209,22 +191,20 @@ public class RestResourceController implements InitializingBean { * * Note that the regular expression in the request mapping accept a UUID as identifier; * - * Please see {@link RestResourceController#findOne(String, String, Integer, String)} for findOne with number as + * Please see {@link RestResourceController#findOne(String, String, Integer)} for findOne with number as * identifier - * and see {@link RestResourceController#findOne(String, String, String, String)} for string as identifier + * and see {@link RestResourceController#findOne(String, String, String)} for string as identifier * * @param apiCategory * @param model * @param uuid - * @param projection * @return */ @RequestMapping(method = RequestMethod.GET, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID) @SuppressWarnings("unchecked") public DSpaceResource findOne(@PathVariable String apiCategory, @PathVariable String model, - @PathVariable UUID uuid, - @RequestParam(required = false) String projection) { - return findOneInternal(apiCategory, model, uuid, projection); + @PathVariable UUID uuid) { + return findOneInternal(apiCategory, model, uuid); } /** @@ -233,13 +213,10 @@ public class RestResourceController implements InitializingBean { * @param apiCategory * @param model * @param id - * @param projection * @return */ private DSpaceResource findOneInternal(String apiCategory, - String model, ID id, - String projection) { - checkModelPluralForm(apiCategory, model); + String model, ID id) { DSpaceRestRepository repository = utils.getResourceRepository(apiCategory, model); RestAddressableModel modelObject = null; try { @@ -250,9 +227,7 @@ public class RestResourceController implements InitializingBean { if (modelObject == null) { throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + id + " not found"); } - DSpaceResource result = repository.wrapResource(modelObject); - linkService.addLinks(result); - return result; + return converter.toResource(modelObject); } /** @@ -267,7 +242,6 @@ public class RestResourceController implements InitializingBean { * @param rel * @param page * @param assembler - * @param projection * @return */ @RequestMapping(method = RequestMethod.GET, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_DIGIT + "/{rel}") @@ -275,9 +249,8 @@ public class RestResourceController implements InitializingBean { @PathVariable String apiCategory, @PathVariable String model, @PathVariable Integer id, @PathVariable String rel, Pageable page, - PagedResourcesAssembler assembler, - @RequestParam(required = false) String projection) { - return findRelInternal(request, response, apiCategory, model, id, rel, page, assembler, projection); + PagedResourcesAssembler assembler) { + return findRelInternal(request, response, apiCategory, model, id, rel, page, assembler); } /** @@ -293,7 +266,6 @@ public class RestResourceController implements InitializingBean { * @param rel * @param page * @param assembler - * @param projection * @return */ @RequestMapping(method = RequestMethod.GET, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG + @@ -302,9 +274,8 @@ public class RestResourceController implements InitializingBean { @PathVariable String apiCategory, @PathVariable String model, @PathVariable String id, @PathVariable String rel, Pageable page, - PagedResourcesAssembler assembler, - @RequestParam(required = false) String projection) { - return findRelInternal(request, response, apiCategory, model, id, rel, page, assembler, projection); + PagedResourcesAssembler assembler) { + return findRelInternal(request, response, apiCategory, model, id, rel, page, assembler); } /** @@ -319,7 +290,6 @@ public class RestResourceController implements InitializingBean { * @param rel * @param page * @param assembler - * @param projection * @return */ @RequestMapping(method = RequestMethod.GET, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID + "/{rel}") @@ -327,9 +297,8 @@ public class RestResourceController implements InitializingBean { @PathVariable String apiCategory, @PathVariable String model, @PathVariable UUID uuid, @PathVariable String rel, Pageable page, - PagedResourcesAssembler assembler, - @RequestParam(required = false) String projection) { - return findRelInternal(request, response, apiCategory, model, uuid, rel, page, assembler, projection); + PagedResourcesAssembler assembler) { + return findRelInternal(request, response, apiCategory, model, uuid, rel, page, assembler); } /** @@ -362,7 +331,6 @@ public class RestResourceController implements InitializingBean { * @param relid * @param page * @param assembler - * @param projection * @return */ @RequestMapping(method = RequestMethod.GET, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG + @@ -371,9 +339,8 @@ public class RestResourceController implements InitializingBean { @PathVariable String apiCategory, @PathVariable String model, @PathVariable String id, @PathVariable String rel, @PathVariable String relid, - Pageable page, PagedResourcesAssembler assembler, - @RequestParam(required = false) String projection) throws Throwable { - return findRelEntryInternal(request, response, apiCategory, model, id, rel, relid, page, assembler, projection); + Pageable page, PagedResourcesAssembler assembler) throws Throwable { + return findRelEntryInternal(request, response, apiCategory, model, id, rel, relid, page, assembler); } @@ -458,8 +425,7 @@ public class RestResourceController implements InitializingBean { if (modelObject == null) { return ControllerUtils.toEmptyResponse(HttpStatus.CREATED); } - DSpaceResource result = repository.wrapResource(modelObject); - linkService.addLinks(result); + DSpaceResource result = converter.toResource(modelObject); //TODO manage HTTPHeader return ControllerUtils.toResponseEntity(HttpStatus.CREATED, null, result); } @@ -491,8 +457,7 @@ public class RestResourceController implements InitializingBean { if (modelObject == null) { return ControllerUtils.toEmptyResponse(HttpStatus.CREATED); } - DSpaceResource result = repository.wrapResource(modelObject); - linkService.addLinks(result); + DSpaceResource result = converter.toResource(modelObject); //TODO manage HTTPHeader return ControllerUtils.toResponseEntity(HttpStatus.CREATED, null, result); } @@ -530,8 +495,7 @@ public class RestResourceController implements InitializingBean { } if (modelObject != null) { - DSpaceResource result = repository.wrapResource(modelObject); - linkService.addLinks(result); + DSpaceResource result = converter.toResource(modelObject); return ControllerUtils.toResponseEntity(HttpStatus.CREATED, null, result); } else { return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); @@ -622,8 +586,7 @@ public class RestResourceController implements InitializingBean { log.error(e.getMessage(), e); return ControllerUtils.toEmptyResponse(HttpStatus.INTERNAL_SERVER_ERROR); } - DSpaceResource result = repository.wrapResource(modelObject); - linkService.addLinks(result); + DSpaceResource result = converter.toResource(modelObject); return ControllerUtils.toResponseEntity(HttpStatus.CREATED, null, result); } @@ -660,8 +623,7 @@ public class RestResourceController implements InitializingBean { List resources = new ArrayList<>(); for (T modelObject : content) { - DSpaceResource result = repository.wrapResource(modelObject); - linkService.addLinks(result); + DSpaceResource result = converter.toResource(modelObject); resources.add(result); } return ControllerUtils.toResponseEntity(HttpStatus.OK, null, Resources.wrap(resources)); @@ -740,8 +702,7 @@ public class RestResourceController implements InitializingBean { log.error(e.getMessage(), e); throw e; } - DSpaceResource result = repository.wrapResource(modelObject); - linkService.addLinks(result); + DSpaceResource result = converter.toResource(modelObject); //TODO manage HTTPHeader return ControllerUtils.toResponseEntity(HttpStatus.OK, null, result); @@ -758,7 +719,6 @@ public class RestResourceController implements InitializingBean { * @param relid * @param page * @param assembler - * @param projection * @return */ private ResourceSupport findRelEntryInternal(HttpServletRequest request, @@ -766,26 +726,24 @@ public class RestResourceController implements InitializingBean { String apiCategory, String model, String id, String rel, String relid, Pageable page, - PagedResourcesAssembler assembler, - String projection) throws Throwable { + PagedResourcesAssembler assembler) + throws Throwable { checkModelPluralForm(apiCategory, model); DSpaceRestRepository repository = utils.getResourceRepository(apiCategory, model); Class domainClass = repository.getDomainClass(); - LinkRest linkRest = utils.getLinkRest(rel, domainClass); + LinkRest linkRest = utils.getClassLevelLinkRest(rel, domainClass); if (linkRest != null) { LinkRestRepository linkRepository = utils.getLinkResourceRepository(apiCategory, model, linkRest.name()); - Method linkMethod = repositoryUtils.getLinkMethod("getResource", linkRepository); - + Method linkMethod = utils.requireMethod(linkRepository.getClass(), "getResource"); try { - Object object = linkMethod.invoke(linkRepository, request, id, relid, page, projection); + Object object = linkMethod.invoke(linkRepository, request, id, relid, page, utils.obtainProjection()); Link link = linkTo(this.getClass(), apiCategory, model).slash(id).slash(rel).slash(relid).withSelfRel(); List result = new ArrayList(); result.add(object); PageImpl pageResult = new PageImpl(result, page, 1); - Page halResources = pageResult.map(linkRepository::wrapResource); - halResources.forEach(linkService::addLinks); + Page halResources = pageResult.map(restObject -> converter.toResource(restObject)); return assembler.toResource(halResources, link); } catch (InvocationTargetException e) { // This catch has been made to resolve the issue that caused AuthorizeDenied exceptions for the methods @@ -812,63 +770,52 @@ public class RestResourceController implements InitializingBean { * @param apiCategory * @param model * @param uuid - * @param rel * @param page * @param assembler - * @param projection * @return */ private ResourceSupport findRelInternal(HttpServletRequest request, HttpServletResponse response, String apiCategory, String model, ID uuid, String subpath, - Pageable page, PagedResourcesAssembler assembler, - String projection) { + Pageable page, + PagedResourcesAssembler assembler) { checkModelPluralForm(apiCategory, model); DSpaceRestRepository repository = utils.getResourceRepository(apiCategory, model); Class domainClass = repository.getDomainClass(); - LinkRest linkRest = utils.getLinkRest(subpath, domainClass); + LinkRest linkRest = utils.getClassLevelLinkRest(subpath, domainClass); PagedResources result; if (linkRest != null) { LinkRestRepository linkRepository = utils.getLinkResourceRepository(apiCategory, model, linkRest.name()); - Method linkMethod = repositoryUtils.getLinkMethod(linkRest.method(), linkRepository); + Method linkMethod = utils.requireMethod(linkRepository.getClass(), linkRest.method()); + try { + if (Page.class.isAssignableFrom(linkMethod.getReturnType())) { + Page pageResult = (Page) linkMethod + .invoke(linkRepository, request, uuid, page, utils.obtainProjection(true)); - if (linkMethod == null) { - // TODO custom exception - throw new RuntimeException( - "Method for relation " + subpath + " not found: " + linkRest.name() + ":" + linkRest.method()); - } else { - try { - if (Page.class.isAssignableFrom(linkMethod.getReturnType())) { - Page pageResult = (Page) linkMethod - .invoke(linkRepository, request, uuid, page, projection); - - Link link = null; - String querystring = request.getQueryString(); - if (querystring != null && querystring.length() > 0) { - link = linkTo(this.getClass(), apiCategory, model).slash(uuid) - .slash(subpath + '?' + querystring).withSelfRel(); - } else { - link = linkTo(this.getClass(), apiCategory, model).slash(uuid).slash(subpath).withSelfRel(); - } - - Page halResources = pageResult.map(linkRepository::wrapResource); - halResources.forEach(linkService::addLinks); - - return assembler.toResource(halResources, link); + Link link = null; + String querystring = request.getQueryString(); + if (querystring != null && querystring.length() > 0) { + link = linkTo(this.getClass(), apiCategory, model).slash(uuid) + .slash(subpath + '?' + querystring).withSelfRel(); } else { - RestModel object = (RestModel) linkMethod.invoke(linkRepository, request, uuid, page, - projection); - Link link = linkTo(this.getClass(), apiCategory, model).slash(uuid).slash(subpath) - .withSelfRel(); - HALResource tmpresult = linkRepository.wrapResource(object); - tmpresult.add(link); - return tmpresult; + link = linkTo(this.getClass(), apiCategory, model).slash(uuid).slash(subpath).withSelfRel(); } - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - throw new RuntimeException(e.getMessage(), e); + + Page halResources = pageResult.map(object -> converter.toResource(object)); + return assembler.toResource(halResources, link); + } else { + RestModel object = (RestModel) linkMethod.invoke(linkRepository, request, uuid, page, + utils.obtainProjection()); + Link link = linkTo(this.getClass(), apiCategory, model).slash(uuid).slash(subpath) + .withSelfRel(); + HALResource tmpresult = converter.toResource(object); + tmpresult.add(link); + return tmpresult; } + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new RuntimeException(e.getMessage(), e); } } RestAddressableModel modelObject = repository.findOne(uuid); @@ -877,8 +824,7 @@ public class RestResourceController implements InitializingBean { throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + uuid + " not found"); } - DSpaceResource resource = repository.wrapResource(modelObject, subpath); - linkService.addLinks(resource); + DSpaceResource resource = converter.toResource(modelObject); String rel = null; @@ -919,16 +865,7 @@ public class RestResourceController implements InitializingBean { .getResourceRepository(fullList.get(0).getCategory(), fullList.get(0).getType()); PageImpl pageResult = new PageImpl(fullList.subList(start, end), page, fullList.size()); - result = assembler.toResource(pageResult.map(resourceRepository::wrapResource)); - - for (Resource subObj : result) { - if (subObj.getContent() instanceof HALResource) { - linkService.addLinks((HALResource) subObj.getContent()); - } - } - return result; - - + return assembler.toResource(pageResult.map(converter::toResource)); } else { if (resource.getEmbeddedResources().get(rel) == null) { response.setStatus(HttpServletResponse.SC_NO_CONTENT); @@ -945,7 +882,6 @@ public class RestResourceController implements InitializingBean { * @param model * @param page * @param assembler - * @param projection * @return */ @RequestMapping(method = RequestMethod.GET) @@ -954,18 +890,15 @@ public class RestResourceController implements InitializingBean { @PathVariable String model, Pageable page, PagedResourcesAssembler assembler, - @RequestParam(required = false) - String projection, HttpServletResponse response) { DSpaceRestRepository repository = utils.getResourceRepository(apiCategory, model); Link link = linkTo(methodOn(this.getClass(), apiCategory, model).findAll(apiCategory, model, - page, assembler, projection, response)) + page, assembler, response)) .withSelfRel(); Page> resources; try { - resources = repository.findAll(page).map(repository::wrapResource); - resources.forEach(linkService::addLinks); + resources = repository.findAll(page).map(converter::toResource); } catch (PaginationException pe) { resources = new PageImpl>(new ArrayList>(), page, pe.getTotal()); } catch (RepositoryMethodNotImplementedException mne) { @@ -1044,18 +977,15 @@ public class RestResourceController implements InitializingBean { if (searchResult == null) { resources = new PageImpl(new ArrayList(), pageable, 0); } else { - resources = ((Page) searchResult).map(repository::wrapResource); + resources = ((Page) searchResult).map(converter::toResource); } - resources.forEach(linkService::addLinks); result = assembler.toResource(resources, link); } else { if (searchResult == null) { response.setStatus(HttpServletResponse.SC_NO_CONTENT); return null; } - DSpaceResource dsResource = repository.wrapResource((T) searchResult); - linkService.addLinks(dsResource); - result = dsResource; + return converter.toResource((T) searchResult); } return result; } @@ -1204,9 +1134,7 @@ public class RestResourceController implements InitializingBean { if (modelObject == null) { throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + id + " not found"); } - DSpaceResource result = repository.wrapResource(modelObject); - linkService.addLinks(result); - return result; + return converter.toResource(modelObject); } /** @@ -1229,9 +1157,6 @@ public class RestResourceController implements InitializingBean { if (modelObject == null) { throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + id + " not found"); } - DSpaceResource result = repository.wrapResource(modelObject); - linkService.addLinks(result); - return result; - + return converter.toResource(modelObject); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RootRestResourceController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RootRestResourceController.java index 0f8d770b37..7cb1854af8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/RootRestResourceController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/RootRestResourceController.java @@ -9,8 +9,8 @@ package org.dspace.app.rest; import javax.servlet.http.HttpServletRequest; +import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.link.HalLinkService; -import org.dspace.app.rest.model.RootRest; import org.dspace.app.rest.model.hateoas.RootResource; import org.dspace.app.rest.repository.RootRestRepository; import org.springframework.beans.factory.annotation.Autowired; @@ -39,13 +39,11 @@ public class RootRestResourceController { @Autowired RootRestRepository rootRestRepository; + @Autowired + ConverterService converter; + @RequestMapping(method = RequestMethod.GET) public RootResource listDefinedEndpoint(HttpServletRequest request) { - - RootRest rootRest = rootRestRepository.getRoot(); - RootResource rootResource = new RootResource(rootRest); - halLinkService.addLinks(rootResource); - - return rootResource; + return converter.toResource(rootRestRepository.getRoot()); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ScriptProcessesController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ScriptProcessesController.java new file mode 100644 index 0000000000..0b0e1922b8 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ScriptProcessesController.java @@ -0,0 +1,62 @@ +/** + * 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 org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.model.ProcessRest; +import org.dspace.app.rest.model.ScriptRest; +import org.dspace.app.rest.model.hateoas.ProcessResource; +import org.dspace.app.rest.repository.ScriptRestRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.webmvc.ControllerUtils; +import org.springframework.hateoas.ResourceSupport; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +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 adds additional subresource methods to allow connecting scripts with processes + */ +@RestController +@RequestMapping("/api/" + ScriptRest.CATEGORY + "/" + ScriptRest.PLURAL_NAME + "/{name}/processes") +public class ScriptProcessesController { + + private static final Logger log = LogManager.getLogger(); + + @Autowired + private ConverterService converter; + + @Autowired + private ScriptRestRepository scriptRestRepository; + + /** + * This method can be called by sending a POST request to the system/scripts/{name}/processes endpoint + * This will start a process for the script that matches the given name + * @param scriptName The name of the script that we want to start a process for + * @return The ProcessResource object for the created process + * @throws Exception If something goes wrong + */ + @RequestMapping(method = RequestMethod.POST) + @PreAuthorize("hasAuthority('ADMIN')") + public ResponseEntity startProcess(@PathVariable(name = "name") String scriptName) + throws Exception { + if (log.isTraceEnabled()) { + log.trace("Starting Process for Script with name: " + scriptName); + } + ProcessRest processRest = scriptRestRepository.startProcess(scriptName); + ProcessResource processResource = converter.toResource(processRest); + return ControllerUtils.toResponseEntity(HttpStatus.ACCEPTED, null, processResource); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/StatisticsRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/StatisticsRestController.java index 6b55879158..26447a7c9b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/StatisticsRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/StatisticsRestController.java @@ -10,6 +10,7 @@ package org.dspace.app.rest; import java.util.Arrays; import java.util.UUID; +import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.link.HalLinkService; import org.dspace.app.rest.model.RestAddressableModel; @@ -47,6 +48,9 @@ public class StatisticsRestController implements InitializingBean { @Autowired private HalLinkService halLinkService; + @Autowired + private ConverterService converter; + @Autowired private StatisticsRestRepository statisticsRestRepository; @@ -65,12 +69,10 @@ public class StatisticsRestController implements InitializingBean { @RequestMapping(method = RequestMethod.GET) public StatisticsSupportResource getStatisticsSupport() throws Exception { - StatisticsSupportRest statisticsSupportRest = statisticsRestRepository.getStatisticsSupport(); - StatisticsSupportResource statisticsSupportResource = new StatisticsSupportResource(statisticsSupportRest); - halLinkService.addLinks(statisticsSupportResource); - return statisticsSupportResource; + return converter.toResource(statisticsSupportRest); } + @RequestMapping(method = RequestMethod.GET, value = "/viewevents/{uuid}") public PagedResources getViewEvent(@PathVariable(name = "uuid") UUID uuid) throws Exception { throw new RepositoryMethodNotImplementedException("No implementation found; Method not allowed!", ""); @@ -93,13 +95,13 @@ public class StatisticsRestController implements InitializingBean { @RequestMapping(method = RequestMethod.POST, value = "/viewevents") public ResponseEntity postViewEvent() throws Exception { - ViewEventResource result = new ViewEventResource(viewEventRestRepository.createViewEvent(), utils); + ViewEventResource result = converter.toResource(viewEventRestRepository.createViewEvent()); return ControllerUtils.toResponseEntity(HttpStatus.CREATED, null, result); } @RequestMapping(method = RequestMethod.POST, value = "/searchevents") public ResponseEntity postSearchEvent() throws Exception { - SearchEventResource result = new SearchEventResource(searchEventRestRepository.createSearchEvent(), utils); + SearchEventResource result = converter.toResource(searchEventRestRepository.createSearchEvent()); return ControllerUtils.toResponseEntity(HttpStatus.CREATED, null, result); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/UUIDLookupRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/UUIDLookupRestController.java index 75d7fb5094..91555ae026 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/UUIDLookupRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/UUIDLookupRestController.java @@ -14,14 +14,14 @@ import java.net.URI; import java.sql.SQLException; import java.util.Arrays; import java.util.UUID; - import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger; -import org.dspace.app.rest.converter.GenericDSpaceObjectConverter; +import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.model.DSpaceObjectRest; import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.rest.utils.Utils; import org.dspace.content.DSpaceObject; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.DSpaceObjectService; @@ -57,6 +57,9 @@ public class UUIDLookupRestController implements InitializingBean { @Autowired private ContentServiceFactory contentServiceFactory; + @Autowired + private Utils utils; + private static final Logger log = Logger.getLogger(UUIDLookupRestController.class); @@ -64,7 +67,7 @@ public class UUIDLookupRestController implements InitializingBean { private DiscoverableEndpointsService discoverableEndpointsService; @Autowired - private GenericDSpaceObjectConverter converter; + private ConverterService converter; @Override public void afterPropertiesSet() throws Exception { @@ -92,7 +95,7 @@ public class UUIDLookupRestController implements InitializingBean { .getDSpaceObjectServices()) { DSpaceObject dso = dSpaceObjectService.find(context, uuid); if (dso != null) { - DSpaceObjectRest dsor = converter.convert(dso); + DSpaceObjectRest dsor = converter.toRest(dso, utils.obtainProjection()); URI link = linkTo(dsor.getController(), dsor.getCategory(), dsor.getTypePlural()) .slash(dsor.getId()).toUri(); response.setStatus(HttpServletResponse.SC_FOUND); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java index 973769f138..4ad8b5743a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AInprogressItemConverter.java @@ -17,6 +17,7 @@ import org.dspace.app.rest.model.AInprogressSubmissionRest; import org.dspace.app.rest.model.ErrorRest; import org.dspace.app.rest.model.SubmissionDefinitionRest; import org.dspace.app.rest.model.SubmissionSectionRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.submit.AbstractRestProcessingStep; import org.dspace.app.rest.submit.SubmissionService; import org.dspace.app.util.SubmissionConfigReader; @@ -47,22 +48,13 @@ public abstract class AInprogressItemConverter { @Override - public AuthorityEntryRest fromModel(Choice choice) { + public AuthorityEntryRest convert(Choice choice, Projection projection) { AuthorityEntryRest entry = new AuthorityEntryRest(); + entry.setProjection(projection); entry.setValue(choice.value); entry.setDisplay(choice.label); entry.setId(choice.authority); @@ -35,7 +36,7 @@ public class AuthorityEntryRestConverter implements DSpaceConverter getModelClass() { + return Choice.class; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityRestConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityRestConverter.java index b07f4d49e7..7e78ef7f14 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityRestConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/AuthorityRestConverter.java @@ -7,8 +7,8 @@ */ package org.dspace.app.rest.converter; -import org.apache.commons.lang3.NotImplementedException; import org.dspace.app.rest.model.AuthorityRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.AuthorityUtils; import org.dspace.content.authority.ChoiceAuthority; import org.springframework.stereotype.Component; @@ -18,7 +18,7 @@ import org.springframework.stereotype.Component; * model and the REST data model * * TODO please do not use this convert but use the wrapper - * {@link AuthorityUtils#convertAuthority(ChoiceAuthority, String)} + * {@link AuthorityUtils#convertAuthority(ChoiceAuthority, String, String)} * * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ @@ -26,8 +26,9 @@ import org.springframework.stereotype.Component; public class AuthorityRestConverter implements DSpaceConverter { @Override - public AuthorityRest fromModel(ChoiceAuthority step) { + public AuthorityRest convert(ChoiceAuthority step, Projection projection) { AuthorityRest authorityRest = new AuthorityRest(); + authorityRest.setProjection(projection); authorityRest.setHierarchical(step.isHierarchical()); authorityRest.setScrollable(step.isScrollable()); authorityRest.setIdentifier(step.hasIdentifier()); @@ -35,7 +36,7 @@ public class AuthorityRestConverter implements DSpaceConverter getModelClass() { + return ChoiceAuthority.class; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BitstreamConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BitstreamConverter.java index 5f507d7380..5f41700dd9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BitstreamConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BitstreamConverter.java @@ -10,9 +10,9 @@ package org.dspace.app.rest.converter; import java.sql.SQLException; import java.util.List; -import org.dspace.app.rest.model.BitstreamFormatRest; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.model.CheckSumRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.springframework.beans.factory.annotation.Autowired; @@ -25,19 +25,14 @@ import org.springframework.stereotype.Component; * @author Andrea Bollini (andrea.bollini at 4science.it) */ @Component -public class BitstreamConverter - extends DSpaceObjectConverter { - @Autowired(required = true) - BitstreamFormatConverter bfConverter; +public class BitstreamConverter extends DSpaceObjectConverter { + + @Autowired + ConverterService converter; @Override - public org.dspace.content.Bitstream toModel(org.dspace.app.rest.model.BitstreamRest obj) { - return super.toModel(obj); - } - - @Override - public BitstreamRest fromModel(org.dspace.content.Bitstream obj) { - BitstreamRest b = super.fromModel(obj); + public BitstreamRest convert(org.dspace.content.Bitstream obj, Projection projection) { + BitstreamRest b = super.convert(obj, projection); b.setSequenceId(obj.getSequenceID()); List bundles = null; try { @@ -53,14 +48,11 @@ public class BitstreamConverter checksum.setCheckSumAlgorithm(obj.getChecksumAlgorithm()); checksum.setValue(obj.getChecksum()); b.setCheckSum(checksum); - BitstreamFormatRest format = null; try { - format = bfConverter.fromModel(obj.getFormat(null)); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); + b.setFormat(converter.toRest(obj.getFormat(null), projection)); + } catch (SQLException e) { + throw new RuntimeException(e); } - b.setFormat(format); b.setSizeBytes(obj.getSizeBytes()); return b; } @@ -71,7 +63,7 @@ public class BitstreamConverter } @Override - protected Class getModelClass() { + public Class getModelClass() { return Bitstream.class; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BitstreamFormatConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BitstreamFormatConverter.java index 07a79f0a8b..2e0c552422 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BitstreamFormatConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BitstreamFormatConverter.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.BitstreamFormatRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.content.BitstreamFormat; import org.dspace.content.service.BitstreamFormatService; import org.springframework.beans.factory.annotation.Autowired; @@ -26,8 +27,9 @@ public class BitstreamFormatConverter implements DSpaceConverter getModelClass() { + return BitstreamFormat.class; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BrowseIndexConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BrowseIndexConverter.java index b49fcab0cb..5e4dec63df 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BrowseIndexConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BrowseIndexConverter.java @@ -11,6 +11,7 @@ import java.util.ArrayList; import java.util.List; import org.dspace.app.rest.model.BrowseIndexRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.browse.BrowseIndex; import org.dspace.sort.SortException; import org.dspace.sort.SortOption; @@ -24,9 +25,11 @@ import org.springframework.stereotype.Component; */ @Component public class BrowseIndexConverter implements DSpaceConverter { + @Override - public BrowseIndexRest fromModel(BrowseIndex obj) { + public BrowseIndexRest convert(BrowseIndex obj, Projection projection) { BrowseIndexRest bir = new BrowseIndexRest(); + bir.setProjection(projection); bir.setId(obj.getName()); bir.setOrder(obj.getDefaultOrder()); bir.setMetadataBrowse(obj.isMetadataIndex()); @@ -53,7 +56,7 @@ public class BrowseIndexConverter implements DSpaceConverter getModelClass() { + return BrowseIndex.class; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BundleConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BundleConverter.java new file mode 100644 index 0000000000..4617e338c3 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/BundleConverter.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.converter; + +import java.util.stream.Collectors; + +import org.dspace.app.rest.model.BitstreamRest; +import org.dspace.app.rest.model.BundleRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.Bundle; +import org.springframework.stereotype.Component; + +@Component +public class BundleConverter + extends DSpaceObjectConverter { + + @Override + public BundleRest convert(Bundle bundle, Projection projection) { + BundleRest bundleRest = super.convert(bundle, projection); + + bundleRest.setBitstreams(bundle.getBitstreams() + .stream() + .map(x -> (BitstreamRest) converter.toRest(x, projection)) + .collect(Collectors.toList())); + + if (bundle.getPrimaryBitstream() != null) { + BitstreamRest primaryBitstreamRest = converter.toRest(bundle.getPrimaryBitstream(), projection); + bundleRest.setPrimaryBitstream(primaryBitstreamRest); + } + + return bundleRest; + } + + @Override + protected BundleRest newInstance() { + return new BundleRest(); + } + + @Override + public Class getModelClass() { + return Bundle.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ClaimedTaskConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ClaimedTaskConverter.java index d448455286..ab23be4c1c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ClaimedTaskConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ClaimedTaskConverter.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.ClaimedTaskRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.discovery.IndexableObject; import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; @@ -22,30 +23,27 @@ import org.springframework.stereotype.Component; */ @Component public class ClaimedTaskConverter - implements IndexableObjectConverter { + implements IndexableObjectConverter { @Autowired - private WorkflowItemConverter workflowItemConverter; - - @Autowired - private EPersonConverter epersonConverter; + private ConverterService converter; @Override - public ClaimedTaskRest fromModel(ClaimedTask obj) { + public ClaimedTaskRest convert(ClaimedTask obj, Projection projection) { ClaimedTaskRest taskRest = new ClaimedTaskRest(); - + taskRest.setProjection(projection); XmlWorkflowItem witem = obj.getWorkflowItem(); taskRest.setId(obj.getID()); - taskRest.setWorkflowitem(workflowItemConverter.convert(witem)); + taskRest.setWorkflowitem(converter.toRest(witem, projection)); taskRest.setAction(obj.getActionID()); taskRest.setStep(obj.getStepID()); - taskRest.setOwner(epersonConverter.convert(obj.getOwner())); + taskRest.setOwner(converter.toRest(obj.getOwner(), projection)); return taskRest; } @Override - public ClaimedTask toModel(ClaimedTaskRest obj) { - return null; + public Class getModelClass() { + return ClaimedTask.class; } @Override @@ -53,4 +51,4 @@ public class ClaimedTaskConverter return object instanceof ClaimedTask; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java index b51b0ff56d..66d949630c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java @@ -16,6 +16,7 @@ import javax.servlet.http.HttpServletRequest; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.ResourcePolicyRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; @@ -44,35 +45,28 @@ public class CollectionConverter private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(CollectionConverter.class); @Autowired - private BitstreamConverter bitstreamConverter; + private ConverterService converter; @Autowired private CollectionService collectionService; @Autowired private RequestService requestService; @Autowired private AuthorizeService authorizeService; - @Autowired - private ResourcePolicyConverter resourcePolicyConverter; @Override - public org.dspace.content.Collection toModel(org.dspace.app.rest.model.CollectionRest obj) { - return (org.dspace.content.Collection) super.toModel(obj); - } - - @Override - public CollectionRest fromModel(org.dspace.content.Collection obj) { - CollectionRest col = (CollectionRest) super.fromModel(obj); + public CollectionRest convert(org.dspace.content.Collection obj, Projection projection) { + CollectionRest col = super.convert(obj, projection); Bitstream logo = obj.getLogo(); if (logo != null) { - col.setLogo(bitstreamConverter.convert(logo)); + col.setLogo(converter.toRest(logo, projection)); } - col.setDefaultAccessConditions(getDefaultBitstreamPoliciesForCollection(obj.getID())); + col.setDefaultAccessConditions(getDefaultBitstreamPoliciesForCollection(obj.getID(), projection)); return col; } - private List getDefaultBitstreamPoliciesForCollection(UUID uuid) { + private List getDefaultBitstreamPoliciesForCollection(UUID uuid, Projection projection) { Context context = null; Request currentRequest = requestService.getCurrentRequest(); @@ -95,7 +89,7 @@ public class CollectionConverter List results = new ArrayList(); for (ResourcePolicy pp : defaultCollectionPolicies) { - ResourcePolicyRest accessCondition = resourcePolicyConverter.convert(pp); + ResourcePolicyRest accessCondition = converter.toRest(pp, projection); if (accessCondition != null) { results.add(accessCondition); } @@ -109,7 +103,7 @@ public class CollectionConverter } @Override - protected Class getModelClass() { + public Class getModelClass() { return org.dspace.content.Collection.class; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java index 09634e30bd..4f386e7fcb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java @@ -12,6 +12,7 @@ import java.util.List; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.CommunityRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; @@ -31,38 +32,29 @@ public class CommunityConverter implements IndexableObjectConverter { @Autowired - private BitstreamConverter bitstreamConverter; - - @Autowired - private CollectionConverter collectionConverter; + private ConverterService converter; @Override - public org.dspace.content.Community toModel(org.dspace.app.rest.model.CommunityRest obj) { - return (org.dspace.content.Community) super.toModel(obj); - } - - @Override - public CommunityRest fromModel(org.dspace.content.Community obj) { - CommunityRest com = (CommunityRest) super.fromModel(obj); + public CommunityRest convert(org.dspace.content.Community obj, Projection projection) { + CommunityRest com = super.convert(obj, projection); Bitstream logo = obj.getLogo(); if (logo != null) { - com.setLogo(bitstreamConverter.convert(logo)); + com.setLogo(converter.toRest(logo, projection)); } List collections = obj.getCollections(); - List collectionsRest = new ArrayList(); + List collectionsRest = new ArrayList<>(); if (collections != null) { for (Collection col : collections) { - CollectionRest colrest = collectionConverter.fromModel(col); - collectionsRest.add(colrest); + collectionsRest.add(converter.toRest(col, projection)); } } com.setCollections(collectionsRest); List subCommunities = obj.getSubcommunities(); - List communityRest = new ArrayList(); + List communityRest = new ArrayList<>(); if (subCommunities != null) { for (Community scom : subCommunities) { - CommunityRest scomrest = this.fromModel(scom); + CommunityRest scomrest = this.convert(scom, projection); communityRest.add(scomrest); } } @@ -77,7 +69,7 @@ public class CommunityConverter } @Override - protected Class getModelClass() { + public Class getModelClass() { return org.dspace.content.Community.class; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java new file mode 100644 index 0000000000..9e4f6c00f1 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java @@ -0,0 +1,303 @@ +/** + * 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 java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nullable; +import javax.annotation.PostConstruct; + +import org.apache.log4j.Logger; +import org.dspace.app.rest.link.HalLinkFactory; +import org.dspace.app.rest.link.HalLinkService; +import org.dspace.app.rest.model.RestAddressableModel; +import org.dspace.app.rest.model.RestModel; +import org.dspace.app.rest.model.hateoas.HALResource; +import org.dspace.app.rest.projection.DefaultProjection; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.utils.Utils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.core.type.filter.AssignableTypeFilter; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.hateoas.Resource; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +/** + * Converts domain objects from the DSpace service layer to rest objects, and from rest objects to resource + * objects, applying {@link Projection}s where applicable. + */ +@Service +public class ConverterService { + + private static final Logger log = Logger.getLogger(ConverterService.class); + + private final Map projectionMap = new HashMap<>(); + + private final Map converterMap = new HashMap<>(); + + private final Map, Constructor> resourceConstructors = new HashMap<>(); + + @Autowired + private Utils utils; + + @Autowired + private HalLinkService halLinkService; + + @Autowired + private List converters; + + @Autowired + private List projections; + + /** + * Converts the given model object to a rest object, using the appropriate {@link DSpaceConverter} and + * the given projection. + *

+ * The projection's {@link Projection#transformModel(Object)} method will be automatically applied + * before conversion. If the rest object is a {@link RestModel}, the projection's + * {@link Projection#transformRest(RestModel)} method will be automatically called after conversion. + *

+ * + * @param modelObject the model object, which may be a JPA entity any other class for which a converter exists. + * @param projection the projection to use. + * @param the type of model object. A converter {@link Component} must exist that takes this as input. + * @param the inferred return type. + * @return the converted object. If it's a {@link RestAddressableModel}, its + * {@link RestAddressableModel#getProjection()} will be set to the given projection. + * @throws IllegalArgumentException if there is no compatible converter. + * @throws ClassCastException if the converter's return type is not compatible with the inferred return type. + */ + public R toRest(M modelObject, Projection projection) { + M transformedModel = projection.transformModel(modelObject); + DSpaceConverter converter = requireConverter(modelObject.getClass()); + R restObject = converter.convert(transformedModel, projection); + if (restObject instanceof RestModel) { + return (R) projection.transformRest((RestModel) restObject); + } + return restObject; + } + + /** + * Converts a list of model objects to a page of rest objects using the given {@link Projection}. + * + * @param modelObjects the list of model objects. + * @param pageable the pageable. + * @param total the total number of items. + * @param projection the projection to use. + * @param the model object class. + * @param the rest object class. + * @return the page. + * @throws IllegalArgumentException if there is no compatible converter. + * @throws ClassCastException if the converter's return type is not compatible with the inferred return type. + */ + public Page toRestPage(List modelObjects, Pageable pageable, long total, Projection projection) { + return new PageImpl<>(modelObjects, pageable, total).map((object) -> toRest(object, projection)); + } + + /** + * Converts a list of model objects to a page of rest objects using the given {@link Projection}. + * + * @param modelObjects the page of model objects. + * @param projection the projection to use. + * @param the model object class. + * @param the rest object class. + * @return the page. + * @throws IllegalArgumentException if there is no compatible converter. + * @throws ClassCastException if the converter's return type is not compatible with the inferred return type. + */ + public Page toRestPage(Page modelObjects, Projection projection) { + return modelObjects.map((object) -> toRest(object, projection)); + } + + /** + * Gets the converter supporting the given class as input. + * + * @param sourceClass the desired converter's input type. + * @param the converter's input type. + * @param the converter's output type. + * @return the converter. + * @throws IllegalArgumentException if there is no such converter. + */ + DSpaceConverter getConverter(Class sourceClass) { + return (DSpaceConverter) requireConverter(sourceClass); + } + + /** + * Converts the given rest object to a {@link HALResource} object. + *

+ * If the rest object is a {@link RestAddressableModel}, the projection returned by + * {@link RestAddressableModel#getProjection()} will be used to determine which optional + * embeds and links will be added, and {@link Projection#transformResource(HALResource)} + * will be automatically called before returning the final, fully converted resource. + *

+ * In all cases, the {@link HalLinkService} will be used immediately after the resource is constructed, + * to ensure all {@link HalLinkFactory}s have had a chance to add links as needed. + *

+ * + * @param restObject the input rest object. + * @param the return type, a subclass of {@link HALResource}. + * @return the fully converted resource, with all automatic links and embeds applied. + * @throws IllegalArgumentException if there is no compatible resource constructor. + * @throws ClassCastException if the resource type is not compatible with the inferred return type. + */ + public T toResource(RestModel restObject) { + T halResource = getResource(restObject); + if (restObject instanceof RestAddressableModel) { + utils.embedOrLinkClassLevelRels(halResource); + halLinkService.addLinks(halResource); + Projection projection = ((RestAddressableModel) restObject).getProjection(); + return projection.transformResource(halResource); + } else { + halLinkService.addLinks(halResource); + } + return halResource; + } + + /** + * Gets the projection with the given name, or the default (no-op) projection if null is given. + * + * @param projectionName the projection name, or {@code null}. + * @return the projection with the given name, or {@link DefaultProjection} if {@code null} is given. + * @throws IllegalArgumentException if a name is provided and such a projection cannot be found. + */ + public Projection getProjection(@Nullable String projectionName) { + return projectionName == null ? Projection.DEFAULT : requireProjection(projectionName); + } + + /** + * Creates and returns an instance of the appropriate {@link HALResource} subclass for the given rest object. + *

+ * Note: Only two forms of constructor are supported for resources that can be created with this method: + * A one-argument constructor taking the wrapped {@link RestModel}, and a two-argument constructor also taking + * a {@link Utils} instance. If both are found in a candidate resource's constructor, the two-argument form + * will be used. + *

+ * + * @param restObject the rest object to wrap. + * @param the return type, a subclass of {@link HALResource}. + * @return a new resource instance of the appropriate type. + */ + private T getResource(RestModel restObject) { + Constructor constructor = resourceConstructors.get(restObject.getClass()); + if (constructor == null) { + throw new IllegalArgumentException("No constructor found to get resource class from " + restObject); + } + try { + if (constructor.getParameterCount() == 2) { + return (T) constructor.newInstance(restObject, utils); + } else { + return (T) constructor.newInstance(restObject); + } + } catch (InvocationTargetException e) { + if (e.getTargetException() instanceof RuntimeException) { + throw (RuntimeException) e.getTargetException(); + } + throw new RuntimeException(e); + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + /** + * Gets the projection with the given name or throws an {@link IllegalArgumentException}. + * + * @param projectionName the projection name. + * @return the projection. + * @throws IllegalArgumentException if not found. + */ + private Projection requireProjection(String projectionName) { + if (!projectionMap.containsKey(projectionName)) { + throw new IllegalArgumentException("No such projection: " + projectionName); + } + return projectionMap.get(projectionName); + } + + /** + * Gets the converter that supports the given source/input class or throws an {@link IllegalArgumentException}. + * + * @param sourceClass the source/input class. + * @return the converter. + * @throws IllegalArgumentException if not found. + */ + private DSpaceConverter requireConverter(Class sourceClass) { + if (converterMap.containsKey(sourceClass)) { + return converterMap.get(sourceClass); + } + for (Class converterSourceClass : converterMap.keySet()) { + if (converterSourceClass.isAssignableFrom(sourceClass)) { + return converterMap.get(converterSourceClass); + } + } + throw new IllegalArgumentException("No converter found to get rest class from " + sourceClass); + } + + /** + * Populates maps of injected components and constructors to be used by this service's public methods. + */ + @PostConstruct + private void initialize() { + // put all available projections in a map keyed by name + for (Projection projection : projections) { + projectionMap.put(projection.getName(), projection); + } + projectionMap.put(Projection.DEFAULT.getName(), Projection.DEFAULT); + + // put all available converters in a map keyed by model (input) class + for (DSpaceConverter converter : converters) { + converterMap.put(converter.getModelClass(), converter); + } + + // scan all resource classes and look for compatible rest classes (by naming convention), + // creating a map of resource constructors keyed by rest class, for later use. + ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); + provider.addIncludeFilter(new AssignableTypeFilter(Resource.class)); + Set beanDefinitions = provider.findCandidateComponents( + HALResource.class.getPackage().getName().replaceAll("\\.", "/")); + for (BeanDefinition beanDefinition : beanDefinitions) { + String resourceClassName = beanDefinition.getBeanClassName(); + String resourceClassSimpleName = resourceClassName.substring(resourceClassName.lastIndexOf(".") + 1); + String restClassSimpleName = resourceClassSimpleName + .replaceAll("ResourceWrapper$", "RestWrapper") + .replaceAll("Resource$", "Rest"); + String restClassName = RestModel.class.getPackage().getName() + "." + restClassSimpleName; + try { + Class restClass = + (Class) Class.forName(restClassName); + Class> resourceClass = + (Class>) Class.forName(resourceClassName); + Constructor compatibleConstructor = null; + for (Constructor constructor : resourceClass.getDeclaredConstructors()) { + if (constructor.getParameterCount() == 2 && constructor.getParameterTypes()[1] == Utils.class) { + compatibleConstructor = constructor; + break; // found preferred constructor + } else if (constructor.getParameterCount() == 1) { + compatibleConstructor = constructor; + } + } + if (compatibleConstructor != null) { + resourceConstructors.put(restClass, compatibleConstructor); + } else { + log.warn("Skipping registration of resource class " + resourceClassName + + "; compatible constructor not found"); + } + } catch (ClassNotFoundException e) { + log.warn("Skipping registration of resource class " + resourceClassName + + "; rest class not found: " + restClassName); + } + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceConverter.java index d195f1ab8d..208904eee4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceConverter.java @@ -7,15 +7,11 @@ */ package org.dspace.app.rest.converter; -import org.springframework.core.convert.converter.Converter; +import org.dspace.app.rest.projection.Projection; -public interface DSpaceConverter extends Converter { - @Override - public default R convert(M source) { - return fromModel(source); - } +public interface DSpaceConverter { - public abstract R fromModel(M obj); + R convert(M modelObject, Projection projection); - public abstract M toModel(R obj); + Class getModelClass(); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java index 42b57983e3..7d3d89ad0c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java @@ -7,6 +7,8 @@ */ package org.dspace.app.rest.converter; +import org.dspace.app.rest.model.MetadataValueList; +import org.dspace.app.rest.projection.Projection; import org.dspace.content.DSpaceObject; import org.springframework.beans.factory.annotation.Autowired; @@ -21,32 +23,22 @@ import org.springframework.beans.factory.annotation.Autowired; public abstract class DSpaceObjectConverter implements DSpaceConverter { - @Autowired(required = true) - private MetadataConverter metadataConverter; + @Autowired + ConverterService converter; @Override - public R fromModel(M obj) { + public R convert(M obj, Projection projection) { R resource = newInstance(); + resource.setProjection(projection); resource.setHandle(obj.getHandle()); if (obj.getID() != null) { resource.setUuid(obj.getID().toString()); } resource.setName(obj.getName()); - resource.setMetadata(metadataConverter.convert(obj.getMetadata())); + MetadataValueList metadataValues = new MetadataValueList(obj.getMetadata()); + resource.setMetadata(converter.toRest(metadataValues, projection)); return resource; } - @Override - public M toModel(R obj) { - return null; - } - - public boolean supportsModel(DSpaceObject object) { - return object != null && object.getClass().equals(getModelClass()); - } - protected abstract R newInstance(); - - protected abstract Class getModelClass(); - -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceRunnableParameterConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceRunnableParameterConverter.java new file mode 100644 index 0000000000..81542147c6 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DSpaceRunnableParameterConverter.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.app.rest.converter; + +import org.dspace.app.rest.model.ParameterValueRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.scripts.DSpaceCommandLineParameter; +import org.springframework.stereotype.Component; + +/** + * This converter will convert an object of {@Link DSpaceCommandLineParameter} to an object + * of {@link ParameterValueRest} + */ +@Component +public class DSpaceRunnableParameterConverter + implements DSpaceConverter { + + @Override + public ParameterValueRest convert(DSpaceCommandLineParameter dSpaceCommandLineParameter, Projection projection) { + ParameterValueRest parameterValueRest = new ParameterValueRest(); + parameterValueRest.setName(dSpaceCommandLineParameter.getName()); + parameterValueRest.setValue(dSpaceCommandLineParameter.getValue()); + return parameterValueRest; + } + + @Override + public Class getModelClass() { + return DSpaceCommandLineParameter.class; + } + + public DSpaceCommandLineParameter toModel(ParameterValueRest parameterValueRest) { + return new DSpaceCommandLineParameter(parameterValueRest.getName(), parameterValueRest.getValue()); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverConfigurationConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverConfigurationConverter.java index 130a89d660..79435dba83 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverConfigurationConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverConfigurationConverter.java @@ -12,6 +12,7 @@ import java.util.stream.Collectors; import org.apache.commons.collections4.CollectionUtils; import org.dspace.app.rest.model.SearchConfigurationRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.discovery.configuration.DiscoveryConfiguration; import org.dspace.discovery.configuration.DiscoverySearchFilter; import org.dspace.discovery.configuration.DiscoverySearchFilterFacet; @@ -24,9 +25,13 @@ import org.springframework.stereotype.Component; * to the convert method. */ @Component -public class DiscoverConfigurationConverter { - public SearchConfigurationRest convert(DiscoveryConfiguration configuration) { +public class DiscoverConfigurationConverter + implements DSpaceConverter { + + @Override + public SearchConfigurationRest convert(DiscoveryConfiguration configuration, Projection projection) { SearchConfigurationRest searchConfigurationRest = new SearchConfigurationRest(); + searchConfigurationRest.setProjection(projection); if (configuration != null) { addSearchFilters(searchConfigurationRest, configuration.getSearchFilters(), configuration.getSidebarFacets()); @@ -36,6 +41,11 @@ public class DiscoverConfigurationConverter { return searchConfigurationRest; } + @Override + public Class getModelClass() { + return DiscoveryConfiguration.class; + } + private void setDefaultSortOption(DiscoveryConfiguration configuration, SearchConfigurationRest searchConfigurationRest) { String defaultSort = configuration.getSearchSortConfiguration().SCORE; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverFacetResultsConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverFacetResultsConverter.java index 524dc3add3..1532502b86 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverFacetResultsConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverFacetResultsConverter.java @@ -15,6 +15,7 @@ import org.dspace.app.rest.model.SearchFacetEntryRest; import org.dspace.app.rest.model.SearchFacetValueRest; import org.dspace.app.rest.model.SearchResultsRest; import org.dspace.app.rest.parameter.SearchFilter; +import org.dspace.app.rest.projection.Projection; import org.dspace.core.Context; import org.dspace.discovery.DiscoverResult; import org.dspace.discovery.configuration.DiscoveryConfiguration; @@ -36,19 +37,20 @@ public class DiscoverFacetResultsConverter { public FacetResultsRest convert(Context context, String facetName, String prefix, String query, String dsoType, String dsoScope, List searchFilters, DiscoverResult searchResult, - DiscoveryConfiguration configuration, Pageable page) { + DiscoveryConfiguration configuration, Pageable page, Projection projection) { FacetResultsRest facetResultsRest = new FacetResultsRest(); + facetResultsRest.setProjection(projection); setRequestInformation(context, facetName, prefix, query, dsoType, dsoScope, searchFilters, searchResult, - configuration, facetResultsRest, page); + configuration, facetResultsRest, page, projection); - addToFacetResultList(facetName, searchResult, facetResultsRest, configuration, page); + addToFacetResultList(facetName, searchResult, facetResultsRest, configuration, page, projection); return facetResultsRest; } private void addToFacetResultList(String facetName, DiscoverResult searchResult, FacetResultsRest facetResultsRest, - DiscoveryConfiguration configuration, Pageable page) { + DiscoveryConfiguration configuration, Pageable page, Projection projection) { DiscoverySearchFilterFacet field = configuration.getSidebarFacet(facetName); List facetValues = searchResult.getFacetResult(field); @@ -59,26 +61,27 @@ public class DiscoverFacetResultsConverter { //We requested one facet value more as the page size. We must make sure to not return the extra value. break; } - SearchFacetValueRest searchFacetValueRest = buildSearchFacetValueRestFromFacetResult(value); + SearchFacetValueRest searchFacetValueRest = buildSearchFacetValueRestFromFacetResult(value, projection); facetResultsRest.addToFacetResultList(searchFacetValueRest); valueCount++; } } - private SearchFacetValueRest buildSearchFacetValueRestFromFacetResult(DiscoverResult.FacetResult value) { - return facetValueConverter.convert(value); + private SearchFacetValueRest buildSearchFacetValueRestFromFacetResult(DiscoverResult.FacetResult value, + Projection projection) { + return facetValueConverter.convert(value, projection); } private void setRequestInformation(Context context, String facetName, String prefix, String query, String dsoType, String dsoScope, List searchFilters, DiscoverResult searchResult, DiscoveryConfiguration configuration, FacetResultsRest facetResultsRest, - Pageable page) { + Pageable page, Projection projection) { facetResultsRest.setQuery(query); facetResultsRest.setPrefix(prefix); facetResultsRest.setScope(dsoScope); facetResultsRest.setDsoType(dsoType); - facetResultsRest.setFacetEntry(convertFacetEntry(facetName, searchResult, configuration, page)); + facetResultsRest.setFacetEntry(convertFacetEntry(facetName, searchResult, configuration, page, projection)); facetResultsRest.setSort(SearchResultsRest.Sorting.fromPage(page)); @@ -91,10 +94,12 @@ public class DiscoverFacetResultsConverter { } private SearchFacetEntryRest convertFacetEntry(final String facetName, final DiscoverResult searchResult, - final DiscoveryConfiguration configuration, final Pageable page) { + final DiscoveryConfiguration configuration, final Pageable page, + final Projection projection) { DiscoverySearchFilterFacet field = configuration.getSidebarFacet(facetName); SearchFacetEntryRest facetEntryRest = new SearchFacetEntryRest(facetName); + facetEntryRest.setProjection(projection); List facetResults = searchResult.getFacetResult(field); if (!facetResults.isEmpty()) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverFacetValueConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverFacetValueConverter.java index c77d9050df..8c2200fa2d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverFacetValueConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverFacetValueConverter.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.SearchFacetValueRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.discovery.DiscoverResult; import org.springframework.stereotype.Component; @@ -17,8 +18,9 @@ import org.springframework.stereotype.Component; @Component public class DiscoverFacetValueConverter { - public SearchFacetValueRest convert(final DiscoverResult.FacetResult value) { + public SearchFacetValueRest convert(final DiscoverResult.FacetResult value, final Projection projection) { SearchFacetValueRest valueRest = new SearchFacetValueRest(); + valueRest.setProjection(projection); valueRest.setLabel(value.getDisplayedValue()); valueRest.setFilterValue(value.getAsFilterQuery()); valueRest.setFilterType(value.getFilterType()); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverFacetsConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverFacetsConverter.java index f7970415e4..ad52f9b002 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverFacetsConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverFacetsConverter.java @@ -16,6 +16,7 @@ import org.dspace.app.rest.model.SearchFacetEntryRest; import org.dspace.app.rest.model.SearchFacetValueRest; import org.dspace.app.rest.model.SearchResultsRest; import org.dspace.app.rest.parameter.SearchFilter; +import org.dspace.app.rest.projection.Projection; import org.dspace.core.Context; import org.dspace.discovery.DiscoverQuery; import org.dspace.discovery.DiscoverResult; @@ -39,13 +40,15 @@ public class DiscoverFacetsConverter { public SearchResultsRest convert(Context context, String query, String dsoType, String configurationName, String dsoScope, List searchFilters, final Pageable page, - DiscoveryConfiguration configuration, DiscoverResult searchResult) { + DiscoveryConfiguration configuration, DiscoverResult searchResult, + Projection projection) { SearchResultsRest searchResultsRest = new SearchResultsRest(); + searchResultsRest.setProjection(projection); setRequestInformation(context, query, dsoType, configurationName, dsoScope, searchFilters, page, searchResultsRest); - addFacetValues(context, searchResult, searchResultsRest, configuration); + addFacetValues(context, searchResult, searchResultsRest, configuration, projection); return searchResultsRest; } @@ -64,13 +67,14 @@ public class DiscoverFacetsConverter { * The DiscoveryConfiguration applied to the query */ public void addFacetValues(Context context, final DiscoverResult searchResult, final SearchResultsRest resultsRest, - final DiscoveryConfiguration configuration) { + final DiscoveryConfiguration configuration, final Projection projection) { List facets = configuration.getSidebarFacets(); for (DiscoverySearchFilterFacet field : CollectionUtils.emptyIfNull(facets)) { List facetValues = searchResult.getFacetResult(field); SearchFacetEntryRest facetEntry = new SearchFacetEntryRest(field.getIndexFieldName()); + facetEntry.setProjection(projection); int valueCount = 0; facetEntry.setHasMore(false); facetEntry.setFacetLimit(field.getFacetLimit()); @@ -84,7 +88,7 @@ public class DiscoverFacetsConverter { // are // more results available. if (valueCount < field.getFacetLimit()) { - SearchFacetValueRest valueRest = facetValueConverter.convert(value); + SearchFacetValueRest valueRest = facetValueConverter.convert(value, projection); facetEntry.addValue(valueRest); } else { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverResultConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverResultConverter.java index 1cd1779e87..8826552015 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverResultConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/DiscoverResultConverter.java @@ -18,6 +18,7 @@ import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.SearchResultEntryRest; import org.dspace.app.rest.model.SearchResultsRest; import org.dspace.app.rest.parameter.SearchFilter; +import org.dspace.app.rest.projection.Projection; import org.dspace.core.Context; import org.dspace.discovery.DiscoverResult; import org.dspace.discovery.IndexableObject; @@ -45,15 +46,17 @@ public class DiscoverResultConverter { public SearchResultsRest convert(final Context context, final String query, final String dsoType, final String configurationName, final String scope, final List searchFilters, final Pageable page, - final DiscoverResult searchResult, final DiscoveryConfiguration configuration) { + final DiscoverResult searchResult, final DiscoveryConfiguration configuration, + final Projection projection) { SearchResultsRest resultsRest = new SearchResultsRest(); + resultsRest.setProjection(projection); setRequestInformation(context, query, dsoType, configurationName, scope, searchFilters, page, resultsRest); - addSearchResults(searchResult, resultsRest); + addSearchResults(searchResult, resultsRest, projection); - addFacetValues(context, searchResult, resultsRest, configuration); + addFacetValues(context, searchResult, resultsRest, configuration, projection); resultsRest.setTotalNumberOfResults(searchResult.getTotalSearchResults()); @@ -61,16 +64,18 @@ public class DiscoverResultConverter { } private void addFacetValues(Context context, final DiscoverResult searchResult, final SearchResultsRest resultsRest, - final DiscoveryConfiguration configuration) { - facetConverter.addFacetValues(context, searchResult, resultsRest, configuration); + final DiscoveryConfiguration configuration, final Projection projection) { + facetConverter.addFacetValues(context, searchResult, resultsRest, configuration, projection); } - private void addSearchResults(final DiscoverResult searchResult, final SearchResultsRest resultsRest) { + private void addSearchResults(final DiscoverResult searchResult, final SearchResultsRest resultsRest, + final Projection projection) { for (IndexableObject dspaceObject : CollectionUtils.emptyIfNull(searchResult.getIndexableObjects())) { SearchResultEntryRest resultEntry = new SearchResultEntryRest(); + resultEntry.setProjection(projection); //Convert the DSpace Object to its REST model - resultEntry.setIndexableObject(convertDSpaceObject(dspaceObject)); + resultEntry.setIndexableObject(convertDSpaceObject(dspaceObject, projection)); //Add hit highlighting for this DSO if present DiscoverResult.IndexableObjectHighlightResult highlightedResults = searchResult @@ -86,10 +91,10 @@ public class DiscoverResultConverter { } } - private RestAddressableModel convertDSpaceObject(final IndexableObject dspaceObject) { + private RestAddressableModel convertDSpaceObject(final IndexableObject dspaceObject, final Projection projection) { for (IndexableObjectConverter converter : converters) { if (converter.supportsModel(dspaceObject)) { - return converter.convert(dspaceObject); + return converter.convert(dspaceObject, projection); } } return null; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/EPersonConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/EPersonConverter.java index 0e39d3a7fb..79c7b9f1f5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/EPersonConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/EPersonConverter.java @@ -14,6 +14,7 @@ import java.util.List; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.EPersonRest; import org.dspace.app.rest.model.GroupRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; @@ -30,17 +31,17 @@ import org.springframework.stereotype.Component; @Component public class EPersonConverter extends DSpaceObjectConverter { - @Autowired(required = true) - private GroupConverter epersonGroupConverter; + @Autowired + private ConverterService converter; - @Autowired(required = true) + @Autowired private GroupService groupService; private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(EPersonConverter.class); @Override - public EPersonRest fromModel(EPerson obj) { - EPersonRest eperson = super.fromModel(obj); + public EPersonRest convert(EPerson obj, Projection projection) { + EPersonRest eperson = super.convert(obj, projection); eperson.setLastActive(obj.getLastActive()); eperson.setNetid(obj.getNetid()); eperson.setCanLogIn(obj.canLogIn()); @@ -51,31 +52,26 @@ public class EPersonConverter extends DSpaceObjectConverter groups = new ArrayList(); + List groups = new ArrayList<>(); for (Group g : groupService.allMemberGroups(context, ePerson)) { - groups.add(epersonGroupConverter.convert(g)); + groups.add(converter.toRest(g, projection)); } eperson.setGroups(groups); return eperson; } - @Override - public EPerson toModel(EPersonRest obj) { - // TODO Auto-generated method stub - return null; - } - @Override protected EPersonRest newInstance() { return new EPersonRest(); } @Override - protected Class getModelClass() { + public Class getModelClass() { return EPerson.class; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/EntityTypeConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/EntityTypeConverter.java index ae2ef646c5..c61a4cf05c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/EntityTypeConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/EntityTypeConverter.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.EntityTypeRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.content.EntityType; import org.springframework.stereotype.Component; @@ -18,29 +19,17 @@ import org.springframework.stereotype.Component; @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) { + @Override + public EntityTypeRest convert(EntityType obj, Projection projection) { EntityTypeRest entityTypeRest = new EntityTypeRest(); + entityTypeRest.setProjection(projection); 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; + @Override + public Class getModelClass() { + return EntityType.class; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/GenericDSpaceObjectConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/GenericDSpaceObjectConverter.java deleted file mode 100644 index c5443a0395..0000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/GenericDSpaceObjectConverter.java +++ /dev/null @@ -1,61 +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.app.rest.converter; - -import java.util.List; - -import org.apache.log4j.Logger; -import org.dspace.app.rest.model.DSpaceObjectRest; -import org.dspace.content.DSpaceObject; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -/** - * This is the converter from/to an unknown DSpaceObject in the DSpace API data model and the - * REST data model - * - * @author Andrea Bollini (andrea.bollini at 4science.it) - */ -@Component -public class GenericDSpaceObjectConverter - extends DSpaceObjectConverter { - - @Autowired - private List converters; - - private static final Logger log = Logger.getLogger(GenericDSpaceObjectConverter.class); - - /** - * Convert a DSpaceObject in its REST representation using a suitable converter - */ - @Override - public DSpaceObjectRest fromModel(org.dspace.content.DSpaceObject dspaceObject) { - for (DSpaceObjectConverter converter : converters) { - if (converter.supportsModel(dspaceObject)) { - return converter.fromModel(dspaceObject); - } - } - return null; - } - - @Override - public org.dspace.content.DSpaceObject toModel(DSpaceObjectRest obj) { - return null; - } - - @Override - protected DSpaceObjectRest newInstance() { - return null; - } - - @Override - protected Class getModelClass() { - return DSpaceObject.class; - } - -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/GroupConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/GroupConverter.java index bb7c1c64c8..592a485555 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/GroupConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/GroupConverter.java @@ -12,6 +12,7 @@ import java.util.List; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.GroupRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.eperson.Group; import org.springframework.stereotype.Component; @@ -27,31 +28,25 @@ public class GroupConverter extends DSpaceObjectConverter groups = new ArrayList(); for (Group g : obj.getMemberGroups()) { - groups.add(convert(g)); + groups.add(convert(g, projection)); } epersongroup.setGroups(groups); return epersongroup; } - @Override - public Group toModel(GroupRest obj) { - // TODO Auto-generated method stub - return null; - } - @Override protected GroupRest newInstance() { return new GroupRest(); } @Override - protected Class getModelClass() { + public Class getModelClass() { return Group.class; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/HarvestedCollectionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/HarvestedCollectionConverter.java index e3176d7563..b989233612 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/HarvestedCollectionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/HarvestedCollectionConverter.java @@ -14,11 +14,11 @@ import org.dspace.app.rest.model.HarvestStatusEnum; import org.dspace.app.rest.model.HarvestTypeEnum; import org.dspace.app.rest.model.HarvestedCollectionRest; import org.dspace.app.rest.model.HarvesterMetadataRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.content.Collection; import org.dspace.harvest.HarvestedCollection; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import sun.reflect.generics.reflectiveObjects.NotImplementedException; /** * This is the converter from/to the HarvestedCollection in the DSpace API data model and the REST data model @@ -29,18 +29,19 @@ import sun.reflect.generics.reflectiveObjects.NotImplementedException; public class HarvestedCollectionConverter implements DSpaceConverter { @Autowired - private CollectionConverter collectionConverter; + private ConverterService converter; @Override - public HarvestedCollectionRest fromModel(HarvestedCollection obj) { + public HarvestedCollectionRest convert(HarvestedCollection obj, Projection projection) { HarvestedCollectionRest harvestedCollectionRest = new HarvestedCollectionRest(); + harvestedCollectionRest.setProjection(projection); if (obj != null) { HarvestTypeEnum harvestTypeEnum = HarvestTypeEnum.fromInt(obj.getHarvestType()); HarvestStatusEnum harvestStatusEnum = HarvestStatusEnum.fromInt(obj.getHarvestStatus()); harvestedCollectionRest.setId(obj.getID()); - harvestedCollectionRest.setCollection(collectionConverter.fromModel(obj.getCollection())); + harvestedCollectionRest.setCollection(converter.toRest(obj.getCollection(), projection)); harvestedCollectionRest.setHarvestType(harvestTypeEnum); harvestedCollectionRest.setHarvestStatus(harvestStatusEnum); harvestedCollectionRest.setMetadataConfigId(obj.getHarvestMetadataConfig()); @@ -59,24 +60,25 @@ public class HarvestedCollectionConverter implements DSpaceConverter> metadata_configs) { - HarvestedCollectionRest harvestedCollectionRest = this.fromModel(obj); + List> metadata_configs, + Projection projection) { + HarvestedCollectionRest harvestedCollectionRest = this.convert(obj, projection); // Add collectionRest to the empty HarvestedCollectionRest so that we can use its uuid later in the linkFactory if (obj == null) { - harvestedCollectionRest.setCollection(collectionConverter.fromModel(collection)); + harvestedCollectionRest.setCollection(converter.toRest(collection, projection)); } HarvesterMetadataRest harvesterMetadataRest = new HarvesterMetadataRest(); + harvesterMetadataRest.setProjection(projection); harvesterMetadataRest.setConfigs(metadata_configs); - harvestedCollectionRest.setMetadataConfigs(harvesterMetadataRest); return harvestedCollectionRest; } @Override - public HarvestedCollection toModel(HarvestedCollectionRest obj) { - throw new NotImplementedException(); + public Class getModelClass() { + return HarvestedCollection.class; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/IndexableObjectConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/IndexableObjectConverter.java index e7887bb9c1..b4e789ab1f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/IndexableObjectConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/IndexableObjectConverter.java @@ -26,6 +26,5 @@ public interface IndexableObjectConverter + extends DSpaceObjectConverter implements IndexableObjectConverter { - @Autowired(required = true) - private CollectionConverter collectionConverter; - @Autowired(required = true) - private BitstreamConverter bitstreamConverter; @Autowired - private RequestService requestService; - @Autowired - private RelationshipService relationshipService; - @Autowired - private RelationshipConverter relationshipConverter; + private ConverterService converter; + @Autowired private ItemService itemService; - @Autowired - private MetadataConverter metadataConverter; private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(ItemConverter.class); @Override - public ItemRest fromModel(org.dspace.content.Item obj) { - ItemRest item = super.fromModel(obj); + public ItemRest convert(Item obj, Projection projection) { + ItemRest item = super.convert(obj, projection); item.setInArchive(obj.isArchived()); item.setDiscoverable(obj.isDiscoverable()); item.setWithdrawn(obj.isWithdrawn()); item.setLastModified(obj.getLastModified()); - try { - Collection c = obj.getOwningCollection(); - if (c != null) { - item.setOwningCollection(collectionConverter.fromModel(c)); - } - } catch (Exception e) { - log.error("Error setting owning collection for item" + item.getHandle(), e); + Collection owningCollection = obj.getOwningCollection(); + if (owningCollection != null) { + item.setOwningCollection(converter.toRest(owningCollection, projection)); } - try { - Collection c = obj.getTemplateItemOf(); - if (c != null) { - item.setTemplateItemOf(collectionConverter.fromModel(c)); - } - } catch (Exception e) { - log.error("Error setting template item of for item " + item.getHandle(), e); + Collection templateItemOf = obj.getTemplateItemOf(); + if (templateItemOf != null) { + item.setTemplateItemOf(converter.toRest(templateItemOf, projection)); } - List bitstreams = new ArrayList(); - for (Bundle bun : obj.getBundles()) { - for (Bitstream bit : bun.getBitstreams()) { - BitstreamRest bitrest = bitstreamConverter.fromModel(bit); - bitstreams.add(bitrest); - } - } - 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)); + item.setBundles(obj.getBundles() + .stream() + .map(x -> (BundleRest) converter.toRest(x, projection)) + .collect(Collectors.toList())); + List fullList = itemService.getMetadata(obj, Item.ANY, Item.ANY, Item.ANY, Item.ANY, true); + MetadataValueList metadataValues = new MetadataValueList(fullList); + item.setMetadata(converter.toRest(metadataValues, projection)); return item; } - @Override - public org.dspace.content.Item toModel(ItemRest obj) { - // TODO Auto-generated method stub - return null; - } - @Override protected ItemRest newInstance() { return new ItemRest(); } @Override - protected Class getModelClass() { + public Class getModelClass() { return Item.class; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/MetadataConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/MetadataConverter.java index 51710f45af..9a8f368add 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/MetadataConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/MetadataConverter.java @@ -17,7 +17,9 @@ import java.util.TreeSet; import java.util.stream.Collectors; import org.dspace.app.rest.model.MetadataRest; +import org.dspace.app.rest.model.MetadataValueList; import org.dspace.app.rest.model.MetadataValueRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.authorize.AuthorizeException; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; @@ -26,39 +28,33 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.DSpaceObjectService; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.convert.converter.Converter; import org.springframework.stereotype.Component; /** * Converter to translate between lists of domain {@link MetadataValue}s and {@link MetadataRest} representations. */ @Component -public class MetadataConverter implements Converter, MetadataRest> { +public class MetadataConverter implements DSpaceConverter { @Autowired private ContentServiceFactory contentServiceFactory; @Autowired - private MetadataValueConverter valueConverter; + private ConverterService converter; - /** - * Gets a rest representation of the given list of domain metadata values. - * - * @param metadataValueList the domain values. - * @return the rest representation. - */ @Override - public MetadataRest convert(List metadataValueList) { + public MetadataRest convert(MetadataValueList metadataValues, + Projection projection) { // Convert each value to a DTO while retaining place order in a map of key -> SortedSet Map> mapOfSortedSets = new HashMap<>(); - for (MetadataValue metadataValue : metadataValueList) { + for (MetadataValue metadataValue : metadataValues) { String key = metadataValue.getMetadataField().toString('.'); SortedSet set = mapOfSortedSets.get(key); if (set == null) { set = new TreeSet<>(Comparator.comparingInt(MetadataValueRest::getPlace)); mapOfSortedSets.put(key, set); } - set.add(valueConverter.convert(metadataValue)); + set.add(converter.toRest(metadataValue, projection)); } MetadataRest metadataRest = new MetadataRest(); @@ -72,6 +68,11 @@ public class MetadataConverter implements Converter, Metadat return metadataRest; } + @Override + public Class getModelClass() { + return MetadataValueList.class; + } + /** * Sets a DSpace object's domain metadata values from a rest representation. * diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/MetadataFieldConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/MetadataFieldConverter.java index 89703f98bb..89da5b0dc9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/MetadataFieldConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/MetadataFieldConverter.java @@ -8,6 +8,8 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.MetadataFieldRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.MetadataField; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -18,24 +20,24 @@ import org.springframework.stereotype.Component; * @author Andrea Bollini (andrea.bollini at 4science.it) */ @Component -public class MetadataFieldConverter implements DSpaceConverter { - @Autowired(required = true) - private MetadataSchemaConverter metadataSchemaConverter; +public class MetadataFieldConverter implements DSpaceConverter { + @Autowired + private ConverterService converter; @Override - public MetadataFieldRest fromModel(org.dspace.content.MetadataField obj) { + public MetadataFieldRest convert(MetadataField obj, Projection projection) { MetadataFieldRest field = new MetadataFieldRest(); + field.setProjection(projection); field.setId(obj.getID()); field.setElement(obj.getElement()); field.setQualifier(obj.getQualifier()); field.setScopeNote(obj.getScopeNote()); - field.setSchema(metadataSchemaConverter.convert(obj.getMetadataSchema())); + field.setSchema(converter.toRest(obj.getMetadataSchema(), projection)); return field; } @Override - public org.dspace.content.MetadataField toModel(MetadataFieldRest obj) { - // TODO Auto-generated method stub - return null; + public Class getModelClass() { + return MetadataField.class; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/MetadataSchemaConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/MetadataSchemaConverter.java index a0847612ca..739351bcef 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/MetadataSchemaConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/MetadataSchemaConverter.java @@ -8,6 +8,8 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.MetadataSchemaRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.MetadataSchema; import org.springframework.stereotype.Component; /** @@ -17,10 +19,11 @@ import org.springframework.stereotype.Component; * @author Andrea Bollini (andrea.bollini at 4science.it) */ @Component -public class MetadataSchemaConverter implements DSpaceConverter { +public class MetadataSchemaConverter implements DSpaceConverter { @Override - public MetadataSchemaRest fromModel(org.dspace.content.MetadataSchema obj) { + public MetadataSchemaRest convert(MetadataSchema obj, Projection projection) { MetadataSchemaRest schema = new MetadataSchemaRest(); + schema.setProjection(projection); schema.setId(obj.getID()); schema.setNamespace(obj.getNamespace()); schema.setPrefix(obj.getName()); @@ -28,8 +31,7 @@ public class MetadataSchemaConverter implements DSpaceConverter getModelClass() { + return MetadataSchema.class; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/MetadataValueConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/MetadataValueConverter.java index 1095e1870a..c5f88998f0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/MetadataValueConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/MetadataValueConverter.java @@ -8,24 +8,18 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.MetadataValueRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.content.MetadataValue; -import org.springframework.core.convert.converter.Converter; import org.springframework.stereotype.Component; /** * Converter to translate between domain {@link MetadataValue}s and {@link MetadataValueRest} representations. */ @Component -public class MetadataValueConverter implements Converter { +public class MetadataValueConverter implements DSpaceConverter { - /** - * Gets a rest representation of the given domain metadata value. - * - * @param metadataValue the domain value. - * @return the rest representation. - */ @Override - public MetadataValueRest convert(MetadataValue metadataValue) { + public MetadataValueRest convert(MetadataValue metadataValue, Projection projection) { MetadataValueRest metadataValueRest = new MetadataValueRest(); metadataValueRest.setValue(metadataValue.getValue()); metadataValueRest.setLanguage(metadataValue.getLanguage()); @@ -34,4 +28,9 @@ public class MetadataValueConverter implements Converter getModelClass() { + return MetadataValue.class; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/PoolTaskConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/PoolTaskConverter.java index 34299c1b0a..e6530d6d6b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/PoolTaskConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/PoolTaskConverter.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.PoolTaskRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.discovery.IndexableObject; import org.dspace.xmlworkflow.storedcomponents.PoolTask; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; @@ -25,26 +26,21 @@ public class PoolTaskConverter implements IndexableObjectConverter { @Autowired - private WorkflowItemConverter workflowItemConverter; - - @Autowired - private EPersonConverter epersonConverter; - - @Autowired - private GroupConverter groupConverter; + private ConverterService converter; @Override - public PoolTaskRest fromModel(PoolTask obj) { + public PoolTaskRest convert(PoolTask obj, Projection projection) { PoolTaskRest taskRest = new PoolTaskRest(); + taskRest.setProjection(projection); XmlWorkflowItem witem = obj.getWorkflowItem(); taskRest.setId(obj.getID()); - taskRest.setWorkflowitem(workflowItemConverter.convert(witem)); + taskRest.setWorkflowitem(converter.toRest(witem, projection)); if (obj.getEperson() != null) { - taskRest.setEperson(epersonConverter.convert(obj.getEperson())); + taskRest.setEperson(converter.toRest(obj.getEperson(), projection)); } if (obj.getGroup() != null) { - taskRest.setGroup(groupConverter.convert(obj.getGroup())); + taskRest.setGroup(converter.toRest(obj.getGroup(), projection)); } taskRest.setAction(obj.getActionID()); taskRest.setStep(obj.getStepID()); @@ -52,8 +48,8 @@ public class PoolTaskConverter } @Override - public PoolTask toModel(PoolTaskRest obj) { - return null; + public Class getModelClass() { + return PoolTask.class; } @Override @@ -61,4 +57,4 @@ public class PoolTaskConverter return object instanceof PoolTask; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ProcessConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ProcessConverter.java new file mode 100644 index 0000000000..4a794c9b85 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ProcessConverter.java @@ -0,0 +1,52 @@ +/** + * 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 java.util.stream.Collectors; + +import org.dspace.app.rest.model.ParameterValueRest; +import org.dspace.app.rest.model.ProcessRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.scripts.Process; +import org.dspace.scripts.service.ProcessService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * This converter will convert an object of {@Link Process} to an object of {@link ProcessRest} + */ +@Component +public class ProcessConverter implements DSpaceConverter { + + @Autowired + private ConverterService converter; + + @Autowired + private ProcessService processService; + + @Override + public ProcessRest convert(Process process, Projection projection) { + ProcessRest processRest = new ProcessRest(); + processRest.setProjection(projection); + processRest.setId(process.getID()); + processRest.setScriptName(process.getName()); + processRest.setProcessId(process.getID()); + processRest.setUserId(process.getEPerson().getID()); + processRest.setProcessStatus(process.getProcessStatus()); + processRest.setStartTime(process.getStartTime()); + processRest.setEndTime(process.getFinishedTime()); + processRest.setParameterRestList(processService.getParameters(process).stream() + .map(x -> (ParameterValueRest) converter.toRest(x, projection)).collect(Collectors.toList())); + return processRest; + } + + @Override + public Class getModelClass() { + return Process.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RelationshipConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RelationshipConverter.java index 8462d50b6d..f9d0cf52ec 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RelationshipConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RelationshipConverter.java @@ -8,10 +8,10 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.RelationshipRest; +import org.dspace.app.rest.projection.Projection; 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 @@ -21,20 +21,15 @@ import sun.reflect.generics.reflectiveObjects.NotImplementedException; public class RelationshipConverter implements DSpaceConverter { @Autowired - private RelationshipTypeConverter relationshipTypeConverter; + private ConverterService converter; - - /** - * 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) { + @Override + public RelationshipRest convert(Relationship obj, Projection projection) { RelationshipRest relationshipRest = new RelationshipRest(); + relationshipRest.setProjection(projection); relationshipRest.setId(obj.getID()); relationshipRest.setLeftId(obj.getLeftItem().getID()); - relationshipRest.setRelationshipType(relationshipTypeConverter.fromModel(obj.getRelationshipType())); + relationshipRest.setRelationshipType(converter.toRest(obj.getRelationshipType(), projection)); relationshipRest.setRightId(obj.getRightItem().getID()); relationshipRest.setLeftPlace(obj.getLeftPlace()); relationshipRest.setRightPlace(obj.getRightPlace()); @@ -43,13 +38,8 @@ public class RelationshipConverter implements DSpaceConverter getModelClass() { + return Relationship.class; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RelationshipTypeConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RelationshipTypeConverter.java index 4ac98ee762..d2f86cf6bb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RelationshipTypeConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RelationshipTypeConverter.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.RelationshipTypeRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.content.RelationshipType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -20,16 +21,12 @@ import org.springframework.stereotype.Component; public class RelationshipTypeConverter implements DSpaceConverter { @Autowired - private EntityTypeConverter entityTypeConverter; + private ConverterService converter; - /** - * 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) { + @Override + public RelationshipTypeRest convert(RelationshipType obj, Projection projection) { RelationshipTypeRest relationshipTypeRest = new RelationshipTypeRest(); + relationshipTypeRest.setProjection(projection); relationshipTypeRest.setId(obj.getID()); relationshipTypeRest.setLeftwardType(obj.getLeftwardType()); @@ -38,20 +35,14 @@ public class RelationshipTypeConverter implements DSpaceConverter getModelClass() { + return RelationshipType.class; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResourcePolicyConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResourcePolicyConverter.java index 1b67d3b9fc..ad3d38c4b3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResourcePolicyConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ResourcePolicyConverter.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.ResourcePolicyRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.ResourcePolicyService; import org.springframework.beans.factory.annotation.Autowired; @@ -26,9 +27,10 @@ public class ResourcePolicyConverter implements DSpaceConverter getModelClass() { + return ResourcePolicy.class; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ScriptConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ScriptConverter.java new file mode 100644 index 0000000000..a307974ee9 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ScriptConverter.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 java.util.LinkedList; +import java.util.List; + +import org.apache.commons.cli.Option; +import org.apache.commons.collections4.CollectionUtils; +import org.dspace.app.rest.model.ParameterRest; +import org.dspace.app.rest.model.ScriptRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.scripts.DSpaceRunnable; +import org.springframework.stereotype.Component; + +/** + * This converter will convert an object of {@Link DSpaceRunnable} to an object + * of {@link ScriptRest} + */ +@Component +public class ScriptConverter implements DSpaceConverter { + + @Override + public ScriptRest convert(DSpaceRunnable script, Projection projection) { + ScriptRest scriptRest = new ScriptRest(); + scriptRest.setProjection(projection); + scriptRest.setDescription(script.getDescription()); + scriptRest.setId(script.getName()); + scriptRest.setName(script.getName()); + + List parameterRestList = new LinkedList<>(); + for (Option option : CollectionUtils.emptyIfNull(script.getOptions().getOptions())) { + ParameterRest parameterRest = new ParameterRest(); + parameterRest.setDescription(option.getDescription()); + parameterRest.setName((option.getOpt() != null ? "-" + option.getOpt() : "--" + option.getLongOpt())); + parameterRest.setType(((Class) option.getType()).getSimpleName()); + parameterRestList.add(parameterRest); + } + scriptRest.setParameterRestList(parameterRestList); + + return scriptRest; + } + + @Override + public Class getModelClass() { + return DSpaceRunnable.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SiteConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SiteConverter.java index 48af0551d8..98de6bb340 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SiteConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SiteConverter.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.SiteRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.content.Site; import org.springframework.stereotype.Component; @@ -18,16 +19,11 @@ import org.springframework.stereotype.Component; * @author Andrea Bollini (andrea.bollini at 4science.it) */ @Component -public class SiteConverter - extends DSpaceObjectConverter { - @Override - public org.dspace.content.Site toModel(org.dspace.app.rest.model.SiteRest obj) { - return (org.dspace.content.Site) super.toModel(obj); - } +public class SiteConverter extends DSpaceObjectConverter { @Override - public SiteRest fromModel(org.dspace.content.Site obj) { - return (SiteRest) super.fromModel(obj); + public SiteRest convert(Site obj, Projection projection) { + return super.convert(obj, projection); } @Override @@ -36,7 +32,7 @@ public class SiteConverter } @Override - protected Class getModelClass() { + public Class getModelClass() { return Site.class; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionDefinitionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionDefinitionConverter.java index cc9c8ddd73..1b4fa05a5b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionDefinitionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionDefinitionConverter.java @@ -13,11 +13,11 @@ import java.util.List; import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; -import org.apache.commons.lang3.NotImplementedException; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.SubmissionDefinitionRest; import org.dspace.app.rest.model.SubmissionSectionRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.util.SubmissionConfig; import org.dspace.app.util.SubmissionConfigReaderException; @@ -47,17 +47,18 @@ public class SubmissionDefinitionConverter implements DSpaceConverter panels = new LinkedList(); for (int idx = 0; idx < obj.getNumberOfSteps(); idx++) { SubmissionStepConfig step = obj.getStep(idx); - SubmissionSectionRest sp = panelConverter.convert(step); + SubmissionSectionRest sp = converter.toRest(step, projection); panels.add(sp); } @@ -68,8 +69,9 @@ public class SubmissionDefinitionConverter implements DSpaceConverter collections = panelConverter.getSubmissionConfigReader() .getCollectionsBySubmissionConfig(context, obj.getSubmissionName()); - List collectionsRest = collections.stream().map( - (collection) -> collectionConverter.convert(collection)).collect(Collectors.toList()); + DSpaceConverter cc = converter.getConverter(Collection.class); + List collectionsRest = collections.stream().map((collection) -> + cc.convert(collection, projection)).collect(Collectors.toList()); sd.setCollections(collectionsRest); } catch (SQLException | IllegalStateException | SubmissionConfigReaderException e) { log.error(e.getMessage(), e); @@ -79,7 +81,7 @@ public class SubmissionDefinitionConverter implements DSpaceConverter getModelClass() { + return SubmissionConfig.class; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java index 485d68f770..f8f34612eb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java @@ -11,7 +11,6 @@ import java.util.ArrayList; import java.util.LinkedList; import java.util.List; -import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.ScopeEnum; import org.dspace.app.rest.model.SubmissionFormFieldRest; @@ -22,6 +21,7 @@ import org.dspace.app.rest.model.SubmissionVisibilityRest; import org.dspace.app.rest.model.VisibilityEnum; import org.dspace.app.rest.model.submit.SelectableMetadata; import org.dspace.app.rest.model.submit.SelectableRelationship; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.repository.SubmissionFormRestRepository; import org.dspace.app.rest.utils.AuthorityUtils; import org.dspace.app.util.DCInput; @@ -51,8 +51,9 @@ public class SubmissionFormConverter implements DSpaceConverter rows = getPage(step, obj.getFormName()); @@ -170,6 +171,7 @@ public class SubmissionFormConverter implements DSpaceConverter getModelClass() { + return DCInputSet.class; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionSectionConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionSectionConverter.java index 43dd014d27..bf683be8a4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionSectionConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionSectionConverter.java @@ -11,6 +11,7 @@ import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.SubmissionSectionRest; import org.dspace.app.rest.model.SubmissionVisibilityRest; import org.dspace.app.rest.model.VisibilityEnum; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.util.SubmissionConfigReader; import org.dspace.app.util.SubmissionConfigReaderException; import org.dspace.app.util.SubmissionStepConfig; @@ -30,8 +31,9 @@ public class SubmissionSectionConverter implements DSpaceConverter getModelClass() { + return SubmissionStepConfig.class; + } + public SubmissionConfigReader getSubmissionConfigReader() throws SubmissionConfigReaderException { if (submissionConfigReader == null) { submissionConfigReader = new SubmissionConfigReader(); } return submissionConfigReader; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowItemConverter.java index ece24ece35..7ed1d27b46 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkflowItemConverter.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.WorkflowItemRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.util.SubmissionConfigReaderException; import org.dspace.discovery.IndexableObject; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; @@ -29,15 +30,16 @@ public class WorkflowItemConverter } @Override - public WorkflowItemRest fromModel(XmlWorkflowItem obj) { + public WorkflowItemRest convert(XmlWorkflowItem obj, Projection projection) { WorkflowItemRest witem = new WorkflowItemRest(); - fillFromModel(obj, witem); + witem.setProjection(projection); + fillFromModel(obj, witem, projection); return witem; } @Override - public XmlWorkflowItem toModel(WorkflowItemRest obj) { - return null; + public Class getModelClass() { + return XmlWorkflowItem.class; } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkspaceItemConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkspaceItemConverter.java index 88ba5522af..24252d1449 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkspaceItemConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/WorkspaceItemConverter.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.converter; import org.dspace.app.rest.model.WorkspaceItemRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.util.SubmissionConfigReaderException; import org.dspace.content.WorkspaceItem; import org.dspace.discovery.IndexableObject; @@ -28,18 +29,22 @@ public class WorkspaceItemConverter } @Override - public WorkspaceItemRest fromModel(org.dspace.content.WorkspaceItem obj) { + public WorkspaceItemRest convert(org.dspace.content.WorkspaceItem obj, Projection projection) { WorkspaceItemRest witem = new WorkspaceItemRest(); - - fillFromModel(obj, witem); + witem.setProjection(projection); + fillFromModel(obj, witem, projection); return witem; } - @Override public org.dspace.content.WorkspaceItem toModel(WorkspaceItemRest obj) { return null; } + @Override + public Class getModelClass() { + return WorkspaceItem.class; + } + @Override public boolean supportsModel(IndexableObject object) { return object instanceof WorkspaceItem; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/AuthorityEntryHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/AuthorityEntryHalLinkFactory.java index a79ade40c3..eeca19617c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/AuthorityEntryHalLinkFactory.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/AuthorityEntryHalLinkFactory.java @@ -41,14 +41,14 @@ public class AuthorityEntryHalLinkFactory extends HalLinkFactory { + + @Autowired + private ConfigurationService configurationService; + + protected void addLinks(ProcessResource halResource, Pageable pageable, LinkedList list) throws Exception { + String dspaceRestUrl = configurationService.getProperty("dspace.restUrl"); + list.add( + buildLink("script", dspaceRestUrl + "/api/system/scripts/" + halResource.getContent().getScriptName())); + + } + + protected Class getControllerClass() { + return RestResourceController.class; + } + + protected Class getResourceClass() { + return ProcessResource.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/relation/EntityTypeHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/relation/EntityTypeHalLinkFactory.java index 99f738ba1e..326caa7eef 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/relation/EntityTypeHalLinkFactory.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/relation/EntityTypeHalLinkFactory.java @@ -27,7 +27,8 @@ import org.springframework.stereotype.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))); + list.add(buildLink("relationshiptypes", getMethodOn().retrieve( + halResource.getContent().getId(), null, null))); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/relation/RelationshipHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/relation/RelationshipHalLinkFactory.java index 9da50be395..9d4e4291f5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/relation/RelationshipHalLinkFactory.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/relation/RelationshipHalLinkFactory.java @@ -29,10 +29,10 @@ public class RelationshipHalLinkFactory extends HalLinkFactory extends @JsonIgnore private EPersonRest submitter; - public AInprogressSubmissionRest() { - super(); - } - public Date getLastModified() { return lastModified; } @@ -90,4 +86,4 @@ public abstract class AInprogressSubmissionRest extends this.collection = collection; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationStatusRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationStatusRest.java index e198d8bb6f..a21f0ead6c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationStatusRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthenticationStatusRest.java @@ -56,7 +56,7 @@ public class AuthenticationStatusRest extends BaseObjectRest { } } - @LinkRest(linkClass = EPersonRest.class, name = "eperson", optional = true) + @LinkRest(linkClass = EPersonRest.class, name = "eperson", linkOptional = true) @JsonIgnore public EPersonRest getEPersonRest() { return ePersonRest; @@ -81,4 +81,4 @@ public class AuthenticationStatusRest extends BaseObjectRest { public void setOkay(boolean okay) { this.okay = okay; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorityEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorityEntryRest.java index 32215ca333..9fcc01d972 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorityEntryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorityEntryRest.java @@ -17,7 +17,7 @@ import org.dspace.app.rest.RestResourceController; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -public class AuthorityEntryRest implements RestAddressableModel { +public class AuthorityEntryRest extends RestAddressableModel { public static final String NAME = "authorityEntry"; private String id; private String display; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorityRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorityRest.java index ddb327c741..c0bc7172c7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorityRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/AuthorityRest.java @@ -15,8 +15,19 @@ import org.dspace.app.rest.RestResourceController; * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest(name = AuthorityRest.ENTRIES, linkClass = AuthorityEntryRest.class, method = "query", optional = true), - @LinkRest(name = AuthorityRest.ENTRY, linkClass = AuthorityEntryRest.class, method = "getResource", optional = true) + @LinkRest(name = AuthorityRest.ENTRIES, + linkClass = AuthorityEntryRest.class, + method = "query", + embedOptional = true, + linkOptional = true + ), + @LinkRest( + name = AuthorityRest.ENTRY, + linkClass = AuthorityEntryRest.class, + method = "getResource", + embedOptional = true, + linkOptional = true + ) }) public class AuthorityRest extends BaseObjectRest { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BaseObjectRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BaseObjectRest.java index 0d82fffd54..6afc04ecd1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BaseObjectRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BaseObjectRest.java @@ -21,7 +21,7 @@ import org.springframework.hateoas.Identifiable; * @param the class of the resource identifier * @author Andrea Bollini (andrea.bollini at 4science.it) */ -public abstract class BaseObjectRest implements Identifiable, RestAddressableModel { +public abstract class BaseObjectRest extends RestAddressableModel implements Identifiable { protected T id; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamFormatRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamFormatRest.java index 4d40b20ec1..1b02a1d89a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamFormatRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BitstreamFormatRest.java @@ -101,4 +101,4 @@ public class BitstreamFormatRest extends BaseObjectRest { public Class getController() { return RestResourceController.class; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseIndexRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseIndexRest.java index 0398b6ab86..76fbcbea25 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseIndexRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BrowseIndexRest.java @@ -19,9 +19,19 @@ import org.dspace.app.rest.RestResourceController; * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest(name = BrowseIndexRest.ITEMS, linkClass = ItemRest.class, method = "listBrowseItems"), - @LinkRest(name = BrowseIndexRest.ENTRIES, linkClass = BrowseEntryRest.class, method = "listBrowseEntries", - optional = true) + @LinkRest( + name = BrowseIndexRest.ITEMS, + linkClass = ItemRest.class, + method = "listBrowseItems", + embedOptional = true + ), + @LinkRest( + name = BrowseIndexRest.ENTRIES, + linkClass = BrowseEntryRest.class, + method = "listBrowseEntries", + embedOptional = true, + linkOptional = true + ) }) public class BrowseIndexRest extends BaseObjectRest { private static final long serialVersionUID = -4870333170249999559L; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BundleRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BundleRest.java new file mode 100644 index 0000000000..b7d862ce41 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BundleRest.java @@ -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/ + */ +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.content.Bitstream; + +/** + * The Bundle REST Resource + * + * @author Jelle Pelgrims (jelle.pelgrims at atmire.com) + */ + +public class BundleRest extends DSpaceObjectRest { + public static final String NAME = "bundle"; + public static final String PLURAL_NAME = "bundles"; + public static final String CATEGORY = RestAddressableModel.CORE; + + private BitstreamRest primaryBitstream; + private List bitstreams; + + @Override + @JsonIgnore + public String getId() { + return super.getId(); + } + + public String getCategory() { + return CATEGORY; + } + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getType() { + return NAME; + } + + @JsonIgnore + @LinkRest(linkClass = Bitstream.class) + public BitstreamRest getPrimaryBitstream() { + return primaryBitstream; + } + + public void setPrimaryBitstream(BitstreamRest primaryBitstream) { + this.primaryBitstream = primaryBitstream; + } + + @LinkRest(linkClass = Bitstream.class) + @JsonIgnore + public List getBitstreams() { + return bitstreams; + } + + public void setBitstreams(List bitstreams) { + this.bitstreams = bitstreams; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ClaimedTaskRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ClaimedTaskRest.java index 3da683c323..8b13aee2d6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ClaimedTaskRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ClaimedTaskRest.java @@ -92,4 +92,4 @@ public class ClaimedTaskRest extends BaseObjectRest { public void setWorkflowitem(WorkflowItemRest workflowitem) { this.workflowitem = workflowitem; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CollectionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CollectionRest.java index f05d5f308f..79faad058f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CollectionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/CollectionRest.java @@ -18,8 +18,13 @@ import com.fasterxml.jackson.annotation.JsonProperty; * @author Andrea Bollini (andrea.bollini at 4science.it) */ @LinksRest(links = { - @LinkRest(name = CollectionRest.LICENSE, linkClass = LicenseRest.class, method = "getLicenseCollection", optional - = true) + @LinkRest( + name = CollectionRest.LICENSE, + linkClass = LicenseRest.class, + method = "getLicenseCollection", + embedOptional = true, + linkOptional = true + ) }) public class CollectionRest extends DSpaceObjectRest { public static final String NAME = "collection"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/DiscoveryResultsRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/DiscoveryResultsRest.java index d3e1dfbe35..d45d948fa6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/DiscoveryResultsRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/DiscoveryResultsRest.java @@ -11,7 +11,6 @@ import java.util.LinkedList; import java.util.List; import com.fasterxml.jackson.annotation.JsonIgnore; - import org.dspace.app.rest.DiscoveryRestController; import org.dspace.app.rest.parameter.SearchFilter; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java index 5798b9289b..96b4286cc4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/EPersonRest.java @@ -123,4 +123,4 @@ public class EPersonRest extends DSpaceObjectRest { return RestResourceController.class; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvestedCollectionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvestedCollectionRest.java index 0b6049e4e1..7fab11e806 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvestedCollectionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HarvestedCollectionRest.java @@ -156,7 +156,7 @@ public class HarvestedCollectionRest extends BaseObjectRest { this.lastHarvested = lastHarvested; } - @LinkRest(linkClass = HarvesterMetadataRest.class, name = "harvestermetadata", optional = true) + @LinkRest(linkClass = HarvesterMetadataRest.class, name = "harvestermetadata", linkOptional = true) @JsonIgnore public HarvesterMetadataRest getMetadataConfigs() { return metadata_configs; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java index abe1d3473f..bdd2da3a66 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ItemRest.java @@ -18,9 +18,19 @@ import com.fasterxml.jackson.annotation.JsonProperty; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ +@LinksRest(links = { + @LinkRest( + name = ItemRest.RELATIONSHIPS, + linkClass = RelationshipRest.class, + method = "getItemRelationships", + embedOptional = true + ) +}) public class ItemRest extends DSpaceObjectRest { public static final String NAME = "item"; + public static final String PLURAL_NAME = "items"; public static final String CATEGORY = RestAddressableModel.CORE; + public static final String RELATIONSHIPS = "relationships"; private boolean inArchive = false; private boolean discoverable = false; private boolean withdrawn = false; @@ -29,9 +39,8 @@ public class ItemRest extends DSpaceObjectRest { private CollectionRest owningCollection; @JsonIgnore private CollectionRest templateItemOf; - List bitstreams; - List relationships; + List bundles; @Override public String getCategory() { @@ -92,23 +101,13 @@ public class ItemRest extends DSpaceObjectRest { this.templateItemOf = templateItemOf; } - @LinkRest(linkClass = BitstreamRest.class) + @LinkRest(linkClass = BundleRest.class) @JsonIgnore - public List getBitstreams() { - return bitstreams; + public List getBundles() { + return bundles; } - public void setBitstreams(List bitstreams) { - this.bitstreams = bitstreams; + public void setBundles(List bundles) { + this.bundles = bundles; } - - @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-server-webapp/src/main/java/org/dspace/app/rest/model/LinkRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/LinkRest.java index e8f5b4a781..b2052415bb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/LinkRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/LinkRest.java @@ -13,8 +13,15 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.dspace.app.rest.model.hateoas.HALResource; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.repository.LinkRestRepository; +import org.dspace.app.rest.utils.Utils; +import org.springframework.stereotype.Component; + /** - * This annotation allows to specify the direct linked REST entities + * Class or method-level annotation to control linking/embedding behavior when a {@link RestModel} + * is wrapped as a {@link HALResource} * * @author Andrea Bollini (andrea.bollini at 4science.it) */ @@ -22,11 +29,71 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LinkRest { + + /** + * The rel name to use for the link and/or embed. + *

+ * This is optional if the annotation is used at the method level. If unspecified at the method level, + * the bean name (inferred by the the name of the method) will be used as the name. + *

+ *

+ * This is required if the annotation is used at the class level. + *

+ * + * @return the name, or the empty string if unspecified by the annotation. + */ String name() default ""; + /** + * The name of the method to invoke in the associated link repository. + *

+ * When this is specified, whether at the class or method level, the value of the resource must be provided + * by a {@link LinkRestRepository}, which is found by its {@link Component} name. See + * {@link Utils#getResourceRepository(String, String)}} for details. + *

+ * + * @return the method name, or the empty string if unspecified by the annotation. + */ String method() default ""; + /** + * The class of object returned by invoking the link method. If a list or page is returned, this should + * specify the inner type. + * + * @return the class. + */ Class linkClass(); - boolean optional() default false; + /** + * Tells whether embedding the resource indicated by this link is optional. + *

+ * If false (the default), it means the resource will always be embedded unless the {@link LinkRestRepository} + * forbids it via {@link LinkRestRepository#isEmbeddableRelation(Object, String)}. + *

+ *

+ * If true, it means the resource will be embedded normally, unless forbidden by the {@link LinkRestRepository} + * or the projection, in use, via {@link Projection#allowOptionalEmbed(HALResource, LinkRest)}. + *

+ * + * @return whether embedding is optional. + */ + boolean embedOptional() default false; + + /** + * Tells whether linking the resource indicated by this link is optional. + *

+ * If false (the default), it means the resource will always be linked. + *

+ *

+ * If true, it means the resource will only be linked if: + *

    + *
  • The resource is embedded, or
  • + *
  • The value returned by the link method is not null and linking is not forbidden by the + * projection in use, via {@link Projection#allowOptionalLink(HALResource, LinkRest)}
  • + *
+ *

+ * + * @return whether linking is optional. + */ + boolean linkOptional() default false; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/LinksRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/LinksRest.java index ef28edcc84..e9d5bd53e4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/LinksRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/LinksRest.java @@ -22,5 +22,5 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LinksRest { - LinkRest[] links(); + LinkRest[] links() default {}; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MappedCollectionRestWrapper.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MappedCollectionRestWrapper.java index d9476dbf1e..328f5d1712 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MappedCollectionRestWrapper.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MappedCollectionRestWrapper.java @@ -16,7 +16,7 @@ import org.dspace.content.Item; /** * The REST object that will define a list of CollectionRest objects to be returned by the REST api */ -public class MappedCollectionRestWrapper implements RestAddressableModel { +public class MappedCollectionRestWrapper extends RestAddressableModel { @JsonIgnore private List mappedCollectionRestList; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MappedItemRestWrapper.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MappedItemRestWrapper.java index 9e0a106551..460c95f149 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MappedItemRestWrapper.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MappedItemRestWrapper.java @@ -16,7 +16,7 @@ import org.dspace.app.rest.MappedItemRestController; /** * The REST object that will define a list of ItemRest objects to be returned by the REST api */ -public class MappedItemRestWrapper implements RestAddressableModel { +public class MappedItemRestWrapper extends RestAddressableModel { @JsonIgnore private List mappedItemRestList; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java index 7e6eabc4d0..966b3afbbe 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java @@ -76,4 +76,4 @@ public class MetadataFieldRest extends BaseObjectRest { public String getCategory() { return CATEGORY; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java index 27229ea429..655d4c86d8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java @@ -54,4 +54,4 @@ public class MetadataSchemaRest extends BaseObjectRest { public String getCategory() { return CATEGORY; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataValueList.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataValueList.java new file mode 100644 index 0000000000..cb0f474420 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/MetadataValueList.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.app.rest.model; + +import java.util.AbstractList; +import java.util.List; + +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.converter.MetadataConverter; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.MetadataValue; + +/** + * Type-safe wrapper for a list of {@link MetadataValue}s for use with {@link MetadataConverter}, + * so it can be invoked properly via calls to {@link ConverterService#toRest(Object, Projection)}. + */ +public class MetadataValueList extends AbstractList { + + private final List list; + + public MetadataValueList(List list) { + this.list = list; + } + + @Override + public MetadataValue get(int index) { + return list.get(index); + } + + @Override + public int size() { + return list.size(); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ParameterRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ParameterRest.java new file mode 100644 index 0000000000..b24544c1bc --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ParameterRest.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; + +/** + * This class serves as a REST representation for a script parameter + */ +public class ParameterRest { + + /** + * The name of the parameter + */ + private String name; + /** + * The description of the parameter + */ + private String description; + /** + * The type of the parameter + */ + private String type; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ParameterValueRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ParameterValueRest.java new file mode 100644 index 0000000000..6c236fa176 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ParameterValueRest.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; + +import org.apache.commons.lang3.StringUtils; + +/** + * This class serves as a REST representation for a paramater with a value given to the script + */ +public class ParameterValueRest { + + /** + * The name of the parameter + */ + private String name; + /** + * The value of the parameter + */ + private String value; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String toString() { + String stringToReturn = ""; + stringToReturn += getName(); + if (StringUtils.isNotBlank(getValue())) { + stringToReturn += " "; + stringToReturn += getValue(); + } + return stringToReturn; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PoolTaskRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PoolTaskRest.java index 4555374520..6ab445659e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PoolTaskRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PoolTaskRest.java @@ -107,4 +107,4 @@ public class PoolTaskRest extends BaseObjectRest { public void setWorkflowitem(WorkflowItemRest workflowitem) { this.workflowitem = workflowitem; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessRest.java new file mode 100644 index 0000000000..6d3ddfae43 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessRest.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 java.util.Date; +import java.util.List; +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.dspace.app.rest.RestResourceController; +import org.dspace.content.ProcessStatus; +import org.dspace.scripts.Process; + +/** + * This class serves as a REST representation for the {@link Process} class + */ +public class ProcessRest extends BaseObjectRest { + public static final String NAME = "process"; + public static final String PLURAL_NAME = "processes"; + public static final String CATEGORY = RestAddressableModel.SYSTEM; + + + public String getCategory() { + return CATEGORY; + } + + public Class getController() { + return RestResourceController.class; + } + + public String getType() { + return NAME; + } + + private String scriptName; + private UUID userId; + private Integer processId; + private Date startTime; + private Date endTime; + private ProcessStatus processStatus; + @JsonProperty(value = "parameters") + private List parameterRestList; + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public Integer getProcessId() { + return processId; + } + + public void setProcessId(Integer processId) { + this.processId = processId; + } + + public ProcessStatus getProcessStatus() { + return processStatus; + } + + public void setProcessStatus(ProcessStatus processStatus) { + this.processStatus = processStatus; + } + + public List getParameterRestList() { + return parameterRestList; + } + + public void setParameterRestList(List parameterRestList) { + this.parameterRestList = parameterRestList; + } + + public Date getStartTime() { + return startTime; + } + + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + + public String getScriptName() { + return scriptName; + } + + public void setScriptName(String scriptName) { + this.scriptName = scriptName; + } + + public Date getEndTime() { + return endTime; + } + + public void setEndTime(Date endTime) { + this.endTime = endTime; + } + + @JsonIgnore + @Override + public Integer getId() { + return id; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipTypeRestWrapper.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipTypeRestWrapper.java index 4a3ffbb6ab..2aa0891005 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipTypeRestWrapper.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RelationshipTypeRestWrapper.java @@ -18,7 +18,7 @@ import org.dspace.app.rest.RelationshipTypeRestController; * RelationshipTypeRest objects * The other methods are generic getters and setters */ -public class RelationshipTypeRestWrapper implements RestAddressableModel { +public class RelationshipTypeRestWrapper extends RestAddressableModel { @JsonIgnore private List relationshipTypeRestList; @@ -26,8 +26,6 @@ public class RelationshipTypeRestWrapper implements RestAddressableModel { private String entityTypeLabel; private Integer entityTypeId; - - public List getRelationshipTypeRestList() { return relationshipTypeRestList; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestAddressableModel.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestAddressableModel.java index 07856f2cb8..e9ae3bd545 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestAddressableModel.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestAddressableModel.java @@ -8,17 +8,29 @@ package org.dspace.app.rest.model; import com.fasterxml.jackson.annotation.JsonIgnore; +import org.dspace.app.rest.projection.Projection; /** * A directly addressable REST resource * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -public interface RestAddressableModel extends RestModel { +public abstract class RestAddressableModel implements RestModel { + + private Projection projection = Projection.DEFAULT; @JsonIgnore - public String getCategory(); + public abstract String getCategory(); @JsonIgnore - public Class getController(); + public abstract Class getController(); + + @JsonIgnore + public Projection getProjection() { + return projection; + } + + public void setProjection(Projection projection) { + this.projection = projection; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java index 6e8e0477e1..39b84f2ee7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RestModel.java @@ -28,6 +28,7 @@ public interface RestModel extends Serializable { public static final String INTEGRATION = "integration"; public static final String STATISTICS = "statistics"; public static final String SUBMISSION = "submission"; + public static final String SYSTEM = "system"; public static final String WORKFLOW = "workflow"; public static final String AUTHORIZATION = "authz"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RootRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RootRest.java index 397fdc9be0..bb20ef9e43 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RootRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/RootRest.java @@ -14,7 +14,7 @@ import org.dspace.app.rest.RootRestResourceController; /** * The purpose of this class is to show the representation of information on the home/root page of the REST API */ -public class RootRest implements RestAddressableModel { +public class RootRest extends RestAddressableModel { public static final String NAME = "root"; public static final String CATEGORY = RestModel.ROOT; private String dspaceURL; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScriptRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScriptRest.java new file mode 100644 index 0000000000..e9c0eb4203 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ScriptRest.java @@ -0,0 +1,70 @@ +/** + * 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.LinkedList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.dspace.app.rest.RestResourceController; + +/** + * This class serves as a REST representation of a Script + */ +public class ScriptRest extends BaseObjectRest { + + public static final String NAME = "script"; + public static final String PLURAL_NAME = "scripts"; + public static final String CATEGORY = RestAddressableModel.SYSTEM; + + private String name; + private String description; + @JsonProperty(value = "parameters") + private List parameterRestList = new LinkedList<>(); + + public String getCategory() { + return CATEGORY; + } + + public Class getController() { + return RestResourceController.class; + } + + public String getType() { + return NAME; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getParameterRestList() { + return parameterRestList; + } + + public void setParameterRestList(List parameterRestList) { + this.parameterRestList = parameterRestList; + } + + public void addToParameterRestList(ParameterRest parameter) { + parameterRestList.add(parameter); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchEventRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchEventRest.java index a6281d961e..e029dbaf99 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchEventRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchEventRest.java @@ -29,19 +29,6 @@ public class SearchEventRest extends BaseObjectRest { private SearchResultsRest.Sorting sort; private PageRest page; - public SearchEventRest(String query, UUID scope, String configuration, - List appliedFilters, - SearchResultsRest.Sorting sort) { - this.query = query; - this.scope = scope; - this.configuration = configuration; - this.appliedFilters = appliedFilters; - this.sort = sort; - } - - public SearchEventRest() { - } - public String getCategory() { return CATEGORY; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetEntryRest.java index e4d73e6f9d..513b4ad54a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetEntryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetEntryRest.java @@ -18,7 +18,7 @@ import org.dspace.discovery.configuration.DiscoverySearchFilterFacet; /** * This class' purpose is to create a container for the information used in the SearchFacetEntryResource */ -public class SearchFacetEntryRest implements RestAddressableModel { +public class SearchFacetEntryRest extends RestAddressableModel { public static final String NAME = "discover"; public static final String CATEGORY = RestModel.DISCOVER; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetValueRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetValueRest.java index abdf3ade03..3a04a207fc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetValueRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchFacetValueRest.java @@ -13,7 +13,7 @@ import org.dspace.app.rest.DiscoveryRestController; /** * This class' purpose is to create a container for the information used in the SearchFacetValueResource */ -public class SearchFacetValueRest implements RestAddressableModel { +public class SearchFacetValueRest extends RestAddressableModel { public static final String NAME = "discover"; public static final String CATEGORY = RestModel.DISCOVER; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchResultEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchResultEntryRest.java index 59826816e0..ac1d94f5f5 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchResultEntryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchResultEntryRest.java @@ -12,13 +12,12 @@ import java.util.List; import java.util.Map; import com.fasterxml.jackson.annotation.JsonIgnore; - import org.dspace.app.rest.DiscoveryRestController; /** * This class' purpose is to create a container for the information in the SearchResultEntryResource */ -public class SearchResultEntryRest implements RestAddressableModel { +public class SearchResultEntryRest extends RestAddressableModel { public static final String NAME = "discover"; public static final String CATEGORY = RestModel.DISCOVER; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchResultsRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchResultsRest.java index 306b9c9ea1..92f091eebe 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchResultsRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SearchResultsRest.java @@ -11,7 +11,6 @@ import java.util.LinkedList; import java.util.List; import com.fasterxml.jackson.annotation.JsonIgnore; - import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -29,7 +28,6 @@ public class SearchResultsRest extends DiscoveryResultsRest { @JsonIgnore List facets; - public List getSearchResults() { return searchResults; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionDefinitionRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionDefinitionRest.java index e027b0b324..ed2d19e7ca 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionDefinitionRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionDefinitionRest.java @@ -86,4 +86,4 @@ public class SubmissionDefinitionRest extends BaseObjectRest { public void setCollections(List collections) { this.collections = collections; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionUploadRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionUploadRest.java index 57fd87c727..4d8504fa0b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionUploadRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionUploadRest.java @@ -97,4 +97,4 @@ public class SubmissionUploadRest extends BaseObjectRest { public void setMetadata(SubmissionFormRest metadata) { this.metadata = metadata; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java index ca4f68e3fa..831baefec1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkflowItemRest.java @@ -32,4 +32,4 @@ public class WorkflowItemRest extends AInprogressSubmissionRest { public Class getController() { return RestResourceController.class; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java index 190bf99cf0..3fac0eeef1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/WorkspaceItemRest.java @@ -32,4 +32,4 @@ public class WorkspaceItemRest extends AInprogressSubmissionRest { public Class getController() { return RestResourceController.class; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthenticationStatusResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthenticationStatusResource.java index 5d64200fc2..8abb35e97a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthenticationStatusResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthenticationStatusResource.java @@ -19,7 +19,7 @@ import org.dspace.app.rest.utils.Utils; */ @RelNameDSpaceResource(AuthenticationStatusRest.NAME) public class AuthenticationStatusResource extends DSpaceResource { - public AuthenticationStatusResource(AuthenticationStatusRest data, Utils utils, String... rels) { - super(data, utils, rels); + public AuthenticationStatusResource(AuthenticationStatusRest data, Utils utils) { + super(data, utils); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthnResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthnResource.java index 9b255b6648..5688919b82 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthnResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthnResource.java @@ -7,8 +7,6 @@ */ package org.dspace.app.rest.model.hateoas; -import java.sql.SQLException; - import org.dspace.app.rest.model.AuthnRest; import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; import org.dspace.app.rest.utils.Utils; @@ -22,7 +20,7 @@ import org.dspace.app.rest.utils.Utils; @RelNameDSpaceResource(AuthnRest.NAME) public class AuthnResource extends DSpaceResource { - public AuthnResource(AuthnRest data, Utils utils, String... rels) throws SQLException { - super(data, utils, rels); + public AuthnResource(AuthnRest data, Utils utils) { + super(data, utils); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityResource.java index cda88f70a1..0e153097b4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/AuthorityResource.java @@ -19,8 +19,8 @@ import org.dspace.app.rest.utils.Utils; */ @RelNameDSpaceResource(AuthorityRest.NAME) public class AuthorityResource extends DSpaceResource { - public AuthorityResource(AuthorityRest sd, Utils utils, String... rels) { - super(sd, utils, rels); + public AuthorityResource(AuthorityRest sd, Utils utils) { + super(sd, utils); if (sd.hasIdentifier()) { add(utils.linkToSubResource(sd, AuthorityRest.ENTRY)); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/BitstreamFormatResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/BitstreamFormatResource.java index a291d13350..1b83479998 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/BitstreamFormatResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/BitstreamFormatResource.java @@ -19,7 +19,7 @@ import org.dspace.app.rest.utils.Utils; */ @RelNameDSpaceResource(BitstreamFormatRest.NAME) public class BitstreamFormatResource extends DSpaceResource { - public BitstreamFormatResource(BitstreamFormatRest bf, Utils utils, String... rels) { - super(bf, utils, rels); + public BitstreamFormatResource(BitstreamFormatRest bf, Utils utils) { + super(bf, utils); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/BitstreamResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/BitstreamResource.java index 91ba363c6c..c456066522 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/BitstreamResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/BitstreamResource.java @@ -19,8 +19,9 @@ import org.dspace.app.rest.utils.Utils; */ @RelNameDSpaceResource(BitstreamRest.NAME) public class BitstreamResource extends DSpaceResource { - public BitstreamResource(BitstreamRest bs, Utils utils, String... rels) { - super(bs, utils, rels); + public BitstreamResource(BitstreamRest bs, Utils utils) { + super(bs, utils); add(utils.linkToSubResource(bs, "content")); + add(utils.linkToSubResource(bs, "bundle")); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/BrowseIndexResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/BrowseIndexResource.java index 407c21a5b9..f6c821595f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/BrowseIndexResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/BrowseIndexResource.java @@ -19,8 +19,8 @@ import org.dspace.app.rest.utils.Utils; */ @RelNameDSpaceResource(BrowseIndexRest.NAME) public class BrowseIndexResource extends DSpaceResource { - public BrowseIndexResource(BrowseIndexRest bix, Utils utils, String... rels) { - super(bix, utils, rels); + public BrowseIndexResource(BrowseIndexRest bix, Utils utils) { + super(bix, utils); // TODO: the following code will force the embedding of items and // entries in the browseIndex we need to find a way to populate the rels // array from the request/projection right now it is always null diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/BundleResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/BundleResource.java new file mode 100644 index 0000000000..bfb2d71e19 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/BundleResource.java @@ -0,0 +1,25 @@ +/** + * 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.BundleRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * Bunde Rest HAL Resource. The HAL Resource wraps the REST Resource + * adding support for the links and embedded resources + * + * @author Jelle Pelgrims (jelle.pelgrims at atmire.com) + */ +@RelNameDSpaceResource(BundleRest.NAME) +public class BundleResource extends DSpaceResource { + public BundleResource(BundleRest data, Utils utils) { + super(data, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ClaimedTaskResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ClaimedTaskResource.java index 688965446c..96a2759646 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ClaimedTaskResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ClaimedTaskResource.java @@ -19,7 +19,7 @@ import org.dspace.app.rest.utils.Utils; */ @RelNameDSpaceResource(ClaimedTaskRest.NAME) public class ClaimedTaskResource extends DSpaceResource { - public ClaimedTaskResource(ClaimedTaskRest witem, Utils utils, String... rels) { - super(witem, utils, rels); + public ClaimedTaskResource(ClaimedTaskRest witem, Utils utils) { + super(witem, utils); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/CollectionResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/CollectionResource.java index 6fd98facbd..db2be4739a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/CollectionResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/CollectionResource.java @@ -19,8 +19,8 @@ import org.dspace.app.rest.utils.Utils; */ @RelNameDSpaceResource(CollectionRest.NAME) public class CollectionResource extends DSpaceResource { - public CollectionResource(CollectionRest collection, Utils utils, String... rels) { - super(collection, utils, rels); + public CollectionResource(CollectionRest collection, Utils utils) { + super(collection, utils); add(utils.linkToSubResource(collection, CollectionRest.LICENSE)); add(utils.linkToSubResource(collection, CollectionRest.HARVEST)); add(utils.linkToSubResource(collection, "mappedItems")); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/CommunityResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/CommunityResource.java index 138f123fea..a774074644 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/CommunityResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/CommunityResource.java @@ -20,7 +20,7 @@ import org.dspace.app.rest.utils.Utils; */ @RelNameDSpaceResource(CommunityRest.NAME) public class CommunityResource extends DSpaceResource { - public CommunityResource(CommunityRest community, Utils utils, String... rels) { - super(community, utils, rels); + public CommunityResource(CommunityRest community, Utils utils) { + super(community, utils); } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/DSpaceResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/DSpaceResource.java index ef06f1360f..dd8fae9b95 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/DSpaceResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/DSpaceResource.java @@ -7,29 +7,9 @@ */ package org.dspace.app.rest.model.hateoas; -import java.beans.IntrospectionException; -import java.beans.Introspector; -import java.beans.PropertyDescriptor; -import java.io.Serializable; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.List; - import com.fasterxml.jackson.annotation.JsonUnwrapped; -import org.apache.commons.lang3.StringUtils; -import org.dspace.app.rest.model.BaseObjectRest; -import org.dspace.app.rest.model.LinkRest; -import org.dspace.app.rest.model.LinksRest; import org.dspace.app.rest.model.RestAddressableModel; -import org.dspace.app.rest.repository.DSpaceRestRepository; -import org.dspace.app.rest.repository.LinkRestRepository; 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; /** * A base class for DSpace Rest HAL Resource. The HAL Resource wraps the REST @@ -39,169 +19,14 @@ import org.springframework.hateoas.Link; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -public abstract class DSpaceResource extends HALResource { +public class DSpaceResource extends HALResource { - public DSpaceResource(T data, Utils utils, String... rels) { + public DSpaceResource(T data, Utils utils) { super(data); - - if (data != null) { - try { - LinksRest links = data.getClass().getDeclaredAnnotation(LinksRest.class); - if (links != null && rels != null) { - List relsList = Arrays.asList(rels); - for (LinkRest linkAnnotation : links.links()) { - if (!relsList.contains(linkAnnotation.name())) { - continue; - } - String name = linkAnnotation.name(); - Link linkToSubResource = utils.linkToSubResource(data, name); - String apiCategory = data.getCategory(); - String model = data.getType(); - LinkRestRepository linkRepository = utils - .getLinkResourceRepository(apiCategory, model, linkAnnotation.name()); - - if (!linkRepository.isEmbeddableRelation(data, linkAnnotation.name())) { - continue; - } - try { - Method[] methods = linkRepository.getClass().getMethods(); - boolean found = false; - for (Method m : methods) { - if (StringUtils.equals(m.getName(), linkAnnotation.method())) { - // TODO add support for single linked object other than for collections - Page pageResult = (Page) m - .invoke(linkRepository, null, ((BaseObjectRest) data).getId(), null, null); - EmbeddedPage ep = new EmbeddedPage(linkToSubResource.getHref(), pageResult, - null, name); - embedded.put(name, ep); - found = true; - } - } - // TODO custom exception - if (!found) { - throw new RuntimeException( - "Method for relation " + linkAnnotation.name() + " not found: " + linkAnnotation - .method()); - } - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - throw new RuntimeException(e.getMessage(), e); - } - } - } - - for (PropertyDescriptor pd : Introspector.getBeanInfo(data.getClass()).getPropertyDescriptors()) { - Method readMethod = pd.getReadMethod(); - String name = pd.getName(); - if (readMethod != null && !"class".equals(name)) { - LinkRest linkAnnotation = AnnotationUtils.findAnnotation(readMethod, LinkRest.class); - - if (linkAnnotation != null) { - if (StringUtils.isNotBlank(linkAnnotation.name())) { - name = linkAnnotation.name(); - } - Link linkToSubResource = utils.linkToSubResource(data, name); - // no method is specified to retrieve the linked object(s) so check if it is already here - if (StringUtils.isBlank(linkAnnotation.method())) { - Object linkedObject = readMethod.invoke(data); - Object wrapObject = linkedObject; - if (linkedObject instanceof RestAddressableModel) { - RestAddressableModel linkedRM = (RestAddressableModel) linkedObject; - wrapObject = utils.getResourceRepository(linkedRM.getCategory(), linkedRM.getType()) - .wrapResource(linkedRM); - - } else { - if (linkedObject instanceof List) { - List linkedRMList = (List) - linkedObject; - if (linkedRMList.size() > 0) { - - DSpaceRestRepository resourceRepository = utils - .getResourceRepository(linkedRMList.get(0).getCategory(), - linkedRMList.get(0).getType()); - // 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); - } else { - PageImpl page = new PageImpl(linkedRMList); - wrapObject = new EmbeddedPage(linkToSubResource.getHref(), page, - linkedRMList, name); - } - } - } - - embedded.put(name, wrapObject); - } else { - // call the link repository - try { - String apiCategory = data.getCategory(); - String model = data.getType(); - LinkRestRepository linkRepository = utils - .getLinkResourceRepository(apiCategory, model, linkAnnotation.name()); - Method[] methods = linkRepository.getClass().getMethods(); - boolean found = false; - for (Method m : methods) { - if (StringUtils.equals(m.getName(), linkAnnotation.method())) { - if (Page.class.isAssignableFrom(m.getReturnType())) { - Page pageResult = (Page) m - .invoke(linkRepository, null, ((BaseObjectRest) data).getId(), null, - null); - EmbeddedPage ep = new EmbeddedPage(linkToSubResource.getHref(), - pageResult, null, name); - embedded.put(name, ep); - } else { - RestAddressableModel object = (RestAddressableModel) m - .invoke(linkRepository, null, ((BaseObjectRest) data).getId(), null, - null); - HALResource ep = linkRepository - .wrapResource(object, linkToSubResource.getHref()); - embedded.put(name, ep); - } - - found = true; - } - } - // TODO custom exception - if (!found) { - throw new RuntimeException("Method for relation " + linkAnnotation - .name() + " not found: " + linkAnnotation.method()); - } - } catch (IllegalAccessException | IllegalArgumentException | - InvocationTargetException e) { - throw new RuntimeException(e.getMessage(), e); - } - } - } else if (RestAddressableModel.class.isAssignableFrom(readMethod.getReturnType())) { - RestAddressableModel linkedObject = (RestAddressableModel) readMethod.invoke(data); - if (linkedObject != null) { - embedded.put(name, - utils.getResourceRepository(linkedObject.getCategory(), - linkedObject.getType()) - .wrapResource(linkedObject)); - } else { - embedded.put(name, null); - } - } - } - } - } catch (IntrospectionException | IllegalArgumentException | IllegalAccessException - | InvocationTargetException e) { - throw new RuntimeException(e.getMessage(), e); - } - } + utils.embedMethodLevelRels(this); } - //Trick to make Java Understand that our content extends RestModel + //Trick to make Java understand that our content extends RestAddressableModel @JsonUnwrapped @Override public T getContent() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/EPersonResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/EPersonResource.java index bc738d4482..e0e89578ce 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/EPersonResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/EPersonResource.java @@ -19,7 +19,7 @@ import org.dspace.app.rest.utils.Utils; */ @RelNameDSpaceResource(EPersonRest.NAME) public class EPersonResource extends DSpaceResource { - public EPersonResource(EPersonRest eperson, Utils utils, String... rels) { - super(eperson, utils, rels); + public EPersonResource(EPersonRest eperson, Utils utils) { + super(eperson, utils); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/EntityTypeResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/EntityTypeResource.java index 9dae4eee15..e62c67c11f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/EntityTypeResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/EntityTypeResource.java @@ -17,7 +17,7 @@ import org.dspace.app.rest.utils.Utils; */ @RelNameDSpaceResource(EntityTypeRest.NAME) public class EntityTypeResource extends DSpaceResource { - public EntityTypeResource(EntityTypeRest data, Utils utils, String... rels) { - super(data, utils, rels); + public EntityTypeResource(EntityTypeRest data, Utils utils) { + super(data, utils); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/GroupResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/GroupResource.java index f85905c611..51bc42dd76 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/GroupResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/GroupResource.java @@ -19,7 +19,7 @@ import org.dspace.app.rest.utils.Utils; */ @RelNameDSpaceResource(GroupRest.NAME) public class GroupResource extends DSpaceResource { - public GroupResource(GroupRest group, Utils utils, String... rels) { - super(group, utils, rels); + public GroupResource(GroupRest group, Utils utils) { + super(group, utils); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/HALResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/HALResource.java index 207aa2713b..eb10b13ac2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/HALResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/HALResource.java @@ -7,13 +7,13 @@ */ package org.dspace.app.rest.model.hateoas; -import java.util.Collection; import java.util.HashMap; import java.util.Map; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonUnwrapped; +import org.springframework.hateoas.Link; import org.springframework.hateoas.Resource; /** @@ -37,18 +37,18 @@ public abstract class HALResource extends Resource { return embedded; } - public void embedResource(String relationship, HALResource resource) { - embedded.put(relationship, resource); - } - public void embedResource(String relationship, EmbeddedPage embeddedPage) { - embedded.put(relationship, embeddedPage); - } - public void embedResource(String relationship, Collection resource) { - embedded.put(relationship, resource); + public void embedResource(String rel, Object object) { + embedded.put(rel, object); } public void setPageHeader(EmbeddedPageHeader page) { this.pageHeader = page; } -} \ No newline at end of file + @Override + public void add(Link link) { + if (!hasLink(link.getRel())) { + super.add(link); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/HarvestedCollectionResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/HarvestedCollectionResource.java index 56abe66e04..aad3d2750a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/HarvestedCollectionResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/HarvestedCollectionResource.java @@ -10,7 +10,6 @@ package org.dspace.app.rest.model.hateoas; import org.dspace.app.rest.model.HarvestedCollectionRest; import org.dspace.app.rest.model.HarvesterMetadataRest; import org.dspace.app.rest.utils.Utils; -import org.springframework.beans.factory.annotation.Autowired; /** * HarvestedCollection Rest HAL Resource. The HAL Resource wraps the REST Resource @@ -20,11 +19,11 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class HarvestedCollectionResource extends HALResource { - @Autowired private Utils utils; - public HarvestedCollectionResource(HarvestedCollectionRest data) { + public HarvestedCollectionResource(HarvestedCollectionRest data, Utils utils) { super(data); + this.utils = utils; embedResource("harvestermetadata", data.getMetadataConfigs()); } @@ -36,4 +35,4 @@ public class HarvestedCollectionResource extends HALResource { - public HarvesterMetadataResource(HarvesterMetadataRest data, Utils utils, String... rels) { - super(data, utils, rels); + public HarvesterMetadataResource(HarvesterMetadataRest data, Utils utils) { + super(data, utils); } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ItemResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ItemResource.java index f47e749113..e7d8294d0d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ItemResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ItemResource.java @@ -19,8 +19,8 @@ import org.dspace.app.rest.utils.Utils; */ @RelNameDSpaceResource(ItemRest.NAME) public class ItemResource extends DSpaceResource { - public ItemResource(ItemRest item, Utils utils, String... rels) { - super(item, utils, rels); + public ItemResource(ItemRest item, Utils utils) { + super(item, utils); add(utils.linkToSubResource(item, "mappedCollections")); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/MappedCollectionResourceWrapper.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/MappedCollectionResourceWrapper.java index 8901583170..6764564225 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/MappedCollectionResourceWrapper.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/MappedCollectionResourceWrapper.java @@ -14,7 +14,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.MappedCollectionRestWrapper; import org.dspace.app.rest.utils.Utils; -import org.springframework.data.domain.Pageable; /** * This class will act as a HALResource object for the MappedCollectionRestWrapper data object and will transform @@ -25,25 +24,15 @@ public class MappedCollectionResourceWrapper extends HALResource collectionResources = new LinkedList<>(); - public MappedCollectionResourceWrapper(MappedCollectionRestWrapper content, Utils utils, Pageable pageable, - String... rels) { + public MappedCollectionResourceWrapper(MappedCollectionRestWrapper content, Utils utils) { super(content); - addEmbeds(content, utils, pageable); + addEmbeds(content, utils); } - private void addEmbeds(final MappedCollectionRestWrapper data, final Utils utils, Pageable pageable) { - + private void addEmbeds(final MappedCollectionRestWrapper data, final Utils utils) { for (CollectionRest collectionRest : data.getMappedCollectionRestList()) { - collectionResources.add(new CollectionResource(collectionRest, utils)); } - - embedResource("mappedCollections", collectionResources); - } - - public List getCollectionResources() { - return collectionResources; - } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/MappedItemResourceWrapper.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/MappedItemResourceWrapper.java index aad7233d84..484a68a38e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/MappedItemResourceWrapper.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/MappedItemResourceWrapper.java @@ -30,7 +30,7 @@ public class MappedItemResourceWrapper extends HALResource { - public MetadataFieldResource(MetadataFieldRest ms, Utils utils, String... rels) { - super(ms, utils, rels); + public MetadataFieldResource(MetadataFieldRest ms, Utils utils) { + super(ms, utils); } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/MetadataSchemaResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/MetadataSchemaResource.java index a7ebe42ad7..3db8a233f8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/MetadataSchemaResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/MetadataSchemaResource.java @@ -19,7 +19,7 @@ import org.dspace.app.rest.utils.Utils; */ @RelNameDSpaceResource(MetadataSchemaRest.NAME) public class MetadataSchemaResource extends DSpaceResource { - public MetadataSchemaResource(MetadataSchemaRest ms, Utils utils, String... rels) { - super(ms, utils, rels); + public MetadataSchemaResource(MetadataSchemaRest ms, Utils utils) { + super(ms, utils); } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/PoolTaskResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/PoolTaskResource.java index 0c0d06b400..021501f8ed 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/PoolTaskResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/PoolTaskResource.java @@ -19,7 +19,7 @@ import org.dspace.app.rest.utils.Utils; */ @RelNameDSpaceResource(PoolTaskRest.NAME) public class PoolTaskResource extends DSpaceResource { - public PoolTaskResource(PoolTaskRest witem, Utils utils, String... rels) { - super(witem, utils, rels); + public PoolTaskResource(PoolTaskRest witem, Utils utils) { + super(witem, utils); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ProcessResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ProcessResource.java new file mode 100644 index 0000000000..c7213dee61 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ProcessResource.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.ProcessRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; +import org.dspace.scripts.Process; + +/** + * The Resource representation of a {@link Process} object + */ +@RelNameDSpaceResource(ProcessRest.NAME) +public class ProcessResource extends DSpaceResource { + public ProcessResource(ProcessRest content, Utils utils) { + super(content, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipResource.java index 8bebdac35d..f35316f3ba 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipResource.java @@ -17,8 +17,8 @@ import org.dspace.app.rest.utils.Utils; */ @RelNameDSpaceResource(RelationshipRest.NAME) public class RelationshipResource extends DSpaceResource { - public RelationshipResource(RelationshipRest data, Utils utils, String... rels) { - super(data, utils, rels); + public RelationshipResource(RelationshipRest data, Utils utils) { + super(data, utils); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipTypeResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipTypeResource.java index 8230eab549..8347554fa2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipTypeResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipTypeResource.java @@ -17,7 +17,7 @@ import org.dspace.app.rest.utils.Utils; */ @RelNameDSpaceResource(RelationshipTypeRest.NAME) public class RelationshipTypeResource extends DSpaceResource { - public RelationshipTypeResource(RelationshipTypeRest data, Utils utils, String... rels) { - super(data, utils, rels); + public RelationshipTypeResource(RelationshipTypeRest data, Utils utils) { + super(data, utils); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipTypeResourceWrapper.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipTypeResourceWrapper.java index 6045946634..57f722d3ca 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipTypeResourceWrapper.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/RelationshipTypeResourceWrapper.java @@ -24,7 +24,6 @@ public class RelationshipTypeResourceWrapper extends HALResource { - public ResourcePolicyResource(ResourcePolicyRest eperson, Utils utils, String... rels) { - super(eperson, utils, rels); + public ResourcePolicyResource(ResourcePolicyRest eperson, Utils utils) { + super(eperson, utils); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ScriptResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ScriptResource.java new file mode 100644 index 0000000000..3c2cd2e71f --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ScriptResource.java @@ -0,0 +1,22 @@ +/** + * 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.ScriptRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * The Resource representation of a Script object + */ +@RelNameDSpaceResource(ScriptRest.NAME) +public class ScriptResource extends DSpaceResource { + public ScriptResource(ScriptRest data, Utils utils) { + super(data, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SearchEventResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SearchEventResource.java index 626658bf57..0ef78cf279 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SearchEventResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SearchEventResource.java @@ -13,7 +13,7 @@ import org.dspace.app.rest.utils.Utils; @RelNameDSpaceResource(SearchEventRest.NAME) public class SearchEventResource extends DSpaceResource { - public SearchEventResource(SearchEventRest data, Utils utils, String... rels) { - super(data, utils, rels); + public SearchEventResource(SearchEventRest data, Utils utils) { + super(data, utils); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SearchResultEntryResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SearchResultEntryResource.java index 75dfabdd2b..550a97a730 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SearchResultEntryResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SearchResultEntryResource.java @@ -9,7 +9,6 @@ package org.dspace.app.rest.model.hateoas; import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.SearchResultEntryRest; -import org.dspace.app.rest.repository.DSpaceRestRepository; import org.dspace.app.rest.utils.Utils; /** @@ -30,9 +29,7 @@ public class SearchResultEntryResource extends HALResource { - public SiteResource(SiteRest site, Utils utils, String... rels) { - super(site, utils, rels); + public SiteResource(SiteRest site, Utils utils) { + super(site, utils); } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionDefinitionResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionDefinitionResource.java index a773597f85..6cf425203b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionDefinitionResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionDefinitionResource.java @@ -19,7 +19,7 @@ import org.dspace.app.rest.utils.Utils; */ @RelNameDSpaceResource(SubmissionDefinitionRest.NAME) public class SubmissionDefinitionResource extends DSpaceResource { - public SubmissionDefinitionResource(SubmissionDefinitionRest sd, Utils utils, String... rels) { - super(sd, utils, rels); + public SubmissionDefinitionResource(SubmissionDefinitionRest sd, Utils utils) { + super(sd, utils); } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionFormResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionFormResource.java index b615793451..466113d375 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionFormResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionFormResource.java @@ -19,7 +19,7 @@ import org.dspace.app.rest.utils.Utils; */ @RelNameDSpaceResource(SubmissionFormRest.NAME) public class SubmissionFormResource extends DSpaceResource { - public SubmissionFormResource(SubmissionFormRest sd, Utils utils, String... rels) { - super(sd, utils, rels); + public SubmissionFormResource(SubmissionFormRest sd, Utils utils) { + super(sd, utils); } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionSectionResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionSectionResource.java index e04314d2e1..84338f6472 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionSectionResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionSectionResource.java @@ -20,7 +20,7 @@ import org.dspace.app.rest.utils.Utils; @RelNameDSpaceResource(SubmissionSectionRest.NAME) public class SubmissionSectionResource extends DSpaceResource { - public SubmissionSectionResource(SubmissionSectionRest sd, Utils utils, String... rels) { - super(sd, utils, rels); + public SubmissionSectionResource(SubmissionSectionRest sd, Utils utils) { + super(sd, utils); } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionUploadResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionUploadResource.java index a3f9edaa5e..2c3af6fd43 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionUploadResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionUploadResource.java @@ -19,7 +19,7 @@ import org.dspace.app.rest.utils.Utils; */ @RelNameDSpaceResource(SubmissionUploadRest.NAME) public class SubmissionUploadResource extends DSpaceResource { - public SubmissionUploadResource(SubmissionUploadRest sd, Utils utils, String... rels) { - super(sd, utils, rels); + public SubmissionUploadResource(SubmissionUploadRest sd, Utils utils) { + super(sd, utils); } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ViewEventResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ViewEventResource.java index b25b112b3f..8fbacac28a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ViewEventResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ViewEventResource.java @@ -13,7 +13,7 @@ import org.dspace.app.rest.utils.Utils; @RelNameDSpaceResource(ViewEventRest.NAME) public class ViewEventResource extends DSpaceResource { - public ViewEventResource(ViewEventRest data, Utils utils, String... rels) { - super(data, utils, rels); + public ViewEventResource(ViewEventRest data, Utils utils) { + super(data, utils); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/WorkflowItemResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/WorkflowItemResource.java index 99a9533cd3..d41e00d3f4 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/WorkflowItemResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/WorkflowItemResource.java @@ -19,7 +19,7 @@ import org.dspace.app.rest.utils.Utils; */ @RelNameDSpaceResource(WorkflowItemRest.NAME) public class WorkflowItemResource extends DSpaceResource { - public WorkflowItemResource(WorkflowItemRest witem, Utils utils, String... rels) { - super(witem, utils, rels); + public WorkflowItemResource(WorkflowItemRest witem, Utils utils) { + super(witem, utils); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/WorkspaceItemResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/WorkspaceItemResource.java index 5ac0d605eb..7a3cff8cfb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/WorkspaceItemResource.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/WorkspaceItemResource.java @@ -19,7 +19,7 @@ import org.dspace.app.rest.utils.Utils; */ @RelNameDSpaceResource(WorkspaceItemRest.NAME) public class WorkspaceItemResource extends DSpaceResource { - public WorkspaceItemResource(WorkspaceItemRest witem, Utils utils, String... rels) { - super(witem, utils, rels); + public WorkspaceItemResource(WorkspaceItemRest witem, Utils utils) { + super(witem, utils); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/submit/SelectableRelationship.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/submit/SelectableRelationship.java index cfc6aa8d71..97117321f1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/submit/SelectableRelationship.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/submit/SelectableRelationship.java @@ -23,6 +23,7 @@ public class SelectableRelationship { private String relationshipType; private String filter; private String searchConfiguration; + private String nameVariants; public void setRelationshipType(String relationshipType) { this.relationshipType = relationshipType; @@ -47,4 +48,12 @@ public class SelectableRelationship { public String getSearchConfiguration() { return searchConfiguration; } + + public void setNameVariants(String nameVariants) { + this.nameVariants = nameVariants; + } + + public String getNameVariants() { + return nameVariants; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/AbstractProjection.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/AbstractProjection.java new file mode 100644 index 0000000000..563f33f48c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/AbstractProjection.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.projection; + +import org.dspace.app.rest.model.LinkRest; +import org.dspace.app.rest.model.RestModel; +import org.dspace.app.rest.model.hateoas.HALResource; + +/** + * Abstract base class for projections. By default each method has no effect unless overridden by a subclass. + */ +public abstract class AbstractProjection implements Projection { + + @Override + public T transformModel(T modelObject) { + return modelObject; + } + + @Override + public T transformRest(T restObject) { + return restObject; + } + + @Override + public T transformResource(T halResource) { + return halResource; + } + + @Override + public boolean allowOptionalEmbed(HALResource halResource, LinkRest linkRest) { + return true; + } + + @Override + public boolean allowOptionalLink(HALResource halResource, LinkRest linkRest) { + return true; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/DefaultProjection.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/DefaultProjection.java new file mode 100644 index 0000000000..0d9395a11f --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/DefaultProjection.java @@ -0,0 +1,21 @@ +/** + * 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.projection; + +/** + * The default projection, which has no effect. + */ +public class DefaultProjection extends AbstractProjection { + + public final static String NAME = "default"; + + @Override + public String getName() { + return NAME; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/ListProjection.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/ListProjection.java new file mode 100644 index 0000000000..9129f5221f --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/ListProjection.java @@ -0,0 +1,31 @@ +/** + * 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.projection; + +import org.dspace.app.rest.model.LinkRest; +import org.dspace.app.rest.model.hateoas.HALResource; +import org.springframework.stereotype.Component; + +/** + * A projection that provides an abbreviated form of any resource that omits all optional embeds. + */ +@Component +public class ListProjection extends AbstractProjection { + + public final static String NAME = "list"; + + @Override + public String getName() { + return NAME; + } + + @Override + public boolean allowOptionalEmbed(HALResource halResource, LinkRest linkRest) { + return false; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/Projection.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/Projection.java new file mode 100644 index 0000000000..290a4e073c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/Projection.java @@ -0,0 +1,135 @@ +/** + * 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.projection; + +import javax.persistence.Entity; + +import org.dspace.app.rest.model.LinkRest; +import org.dspace.app.rest.model.RestModel; +import org.dspace.app.rest.model.hateoas.HALResource; +import org.dspace.app.rest.repository.DSpaceRestRepository; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.RestController; + +/** + * A pluggable, uniquely-named {@link Component} that provides a way to change how a domain object is represented, + * at one or more points in its lifecycle on the way to its being exposed via the REST API. + * + *

The object lifecycle

+ * + *

+ * While fulfilling a typical REST request, a DSpace domain object takes three major forms, in order: + *

+ * + *
    + *
  • A model object provided by some service. This is typically a JPA {@link Entity}.
  • + *
  • A {@link RestModel} object provided by a {@link DSpaceRestRepository}.
  • + *
  • A {@link HALResource} object provided by the {@link RestController} to Spring, which then + * serializes it as JSON for the client to consume.
  • + *
+ * + *

What a projection can modify, and when

+ * + * A {@code Projection} implementation is capable of adding to or omitting information from the object + * in any of these forms, at the following points in time: + * + *
    + *
  • Before it is converted to a {@link RestModel}, the projection may modify it + * via {@link #transformModel(Object)}.
  • + *
  • After it is converted to a {@link RestModel}, the projection may modify it + * via {@link #transformRest(RestModel)}.
  • + *
  • During conversion to a {@link HALResource}, the projection may opt out of certain annotation-discovered + * HAL embeds and links via {@link #allowOptionalEmbed(HALResource, LinkRest)} + * and {@link #allowOptionalLink(HALResource, LinkRest)}.
  • + *
  • After conversion to a {@link HALResource}, the projection may modify it + * via {@link #transformResource(HALResource)}.
  • + *
+ * + *

How a projection is chosen

+ * + * When a REST request is made, the use of a projection may be explicit, as when it is provided as an argument + * to the request, e.g. {@code /items/{uuid}?projection={projectionName}}. It may also be implicit, as when the + * {@link ListProjection} is used automatically, in order to provide an abbreviated representation when serving + * a collection of resources. + */ +public interface Projection { + + /** + * The default projection. + */ + Projection DEFAULT = new DefaultProjection(); + + /** + * Gets the projection name. + * + * @return the name, which is a unique alphanumeric string. + */ + String getName(); + + /** + * Transforms the original model object (e.g. JPA entity) before conversion to a {@link RestModel}. + * + * This is a good place to omit data for certain properties that should not be included in the object's + * representation as a {@link HALResource}. Omitting these properties early helps to prevent unnecessary + * database calls for lazy-loaded properties that are unwanted for the projection. + * + * @param modelObject the original model object, which may be of any type. + * @param the return type, which must be the same type as the given model object. + * @return the transformed model object, or the original, if the projection does not modify it. + */ + T transformModel(T modelObject); + + /** + * Transforms the rest object after it was converted from a model object. + * + * This may add data to, or omit data from the rest representation of the object. + * + * @param restObject the rest object. + * @param the return type, which must be of the same type as the given rest object. + * @return the transformed rest object, or the original, if the projection does not modify it. + */ + T transformRest(T restObject); + + /** + * Transforms the resource object after it has been constructed and any constructor or annotation-based + * links and embeds have been added. + * + * This may add data to, or omit data from the HAL resource representation of the object. + * + * @param halResource the resource object. + * @param the return type, which must be of the same type as the given resource object. + * @return the transformed resource object, or the original, if the projection does not modify it. + */ + T transformResource(T halResource); + + /** + * Tells whether this projection permits the embedding of a particular optionally-embeddable related resource. + * + * Optionally-embeddable related resources, discovered through {@link LinkRest} annotations, are normally + * automatically embedded. This method gives the projection an opportunity to opt out of some or all such embeds, + * by returning {@code false}. + * + * @param halResource the resource from which the embed may or may not be made. + * @param linkRest the LinkRest annotation through which the related resource was discovered on the rest object. + * @return true if allowed, false otherwise. + */ + boolean allowOptionalEmbed(HALResource halResource, LinkRest linkRest); + + /** + * Tells whether this projection permits the linking of a particular optionally-linkable related resource. + * + * Optionally-linkable related resources, discovered through {@link LinkRest} annotations, are normally + * automatically linked. This method gives the projection an opportunity to opt out of some or all such links, + * by returning {@code false}. + * + * @param halResource the resource from which the embed may or may not be made. + * @param linkRest the LinkRest annotation through which the related resource was discovered on the rest object. + * @return true if allowed, false otherwise. + */ + boolean allowOptionalLink(HALResource halResource, LinkRest linkRest); +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java index 8b37dbf79c..a64f8af5df 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AbstractDSpaceRestRepository.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest.repository; +import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.app.rest.utils.Utils; import org.dspace.core.Context; @@ -26,6 +27,9 @@ public abstract class AbstractDSpaceRestRepository { @Autowired protected Utils utils; + @Autowired + protected ConverterService converter; + protected RequestService requestService = new DSpace().getRequestService(); protected Context obtainContext() { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityEntryLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityEntryLinkRepository.java index e7f3f2f017..17ae198903 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityEntryLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityEntryLinkRepository.java @@ -16,8 +16,7 @@ import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.AuthorityEntryRest; import org.dspace.app.rest.model.AuthorityRest; -import org.dspace.app.rest.model.hateoas.AuthorityEntryResource; -import org.dspace.app.rest.model.hateoas.HALResource; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.AuthorityUtils; import org.dspace.content.Collection; import org.dspace.content.authority.Choice; @@ -39,7 +38,7 @@ import org.springframework.stereotype.Component; */ @Component(AuthorityRest.CATEGORY + "." + AuthorityRest.NAME + "." + AuthorityRest.ENTRIES) public class AuthorityEntryLinkRepository extends AbstractDSpaceRestRepository - implements LinkRestRepository { + implements LinkRestRepository { @Autowired private ChoiceAuthorityService cas; @@ -50,14 +49,9 @@ public class AuthorityEntryLinkRepository extends AbstractDSpaceRestRepository @Autowired private AuthorityUtils authorityUtils; - @Override - public HALResource wrapResource(AuthorityEntryRest model, String... rels) { - return new AuthorityEntryResource(model); - } - @PreAuthorize("hasAuthority('AUTHENTICATED')") public Page query(HttpServletRequest request, String name, - Pageable pageable, String projection) { + Pageable pageable, Projection projection) { Context context = obtainContext(); String query = request.getParameter("query"); String metadata = request.getParameter("metadata"); @@ -70,17 +64,17 @@ public class AuthorityEntryLinkRepository extends AbstractDSpaceRestRepository throw new RuntimeException(e); } } - List results = new ArrayList(); + List results = new ArrayList<>(); if (StringUtils.isNotBlank(metadata)) { String[] tokens = org.dspace.core.Utils.tokenize(metadata); String fieldKey = org.dspace.core.Utils.standardize(tokens[0], tokens[1], tokens[2], "_"); Choices choices = cas.getMatches(fieldKey, query, collection, pageable.getOffset(), pageable.getPageSize(), context.getCurrentLocale().toString()); for (Choice value : choices.values) { - results.add(authorityUtils.convertEntry(value, name)); + results.add(authorityUtils.convertEntry(value, name, projection)); } } - return new PageImpl(results, pageable, results.size()); + return new PageImpl<>(results, pageable, results.size()); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityEntryValueLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityEntryValueLinkRepository.java index 4409f46c76..24852b9a09 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityEntryValueLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityEntryValueLinkRepository.java @@ -11,8 +11,7 @@ import javax.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.AuthorityEntryRest; import org.dspace.app.rest.model.AuthorityRest; -import org.dspace.app.rest.model.hateoas.AuthorityEntryResource; -import org.dspace.app.rest.model.hateoas.HALResource; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.AuthorityUtils; import org.dspace.content.authority.Choice; import org.dspace.content.authority.ChoiceAuthority; @@ -31,7 +30,7 @@ import org.springframework.stereotype.Component; */ @Component(AuthorityRest.CATEGORY + "." + AuthorityRest.NAME + "." + AuthorityRest.ENTRY) public class AuthorityEntryValueLinkRepository extends AbstractDSpaceRestRepository - implements LinkRestRepository { + implements LinkRestRepository { @Autowired private ChoiceAuthorityService cas; @@ -39,21 +38,16 @@ public class AuthorityEntryValueLinkRepository extends AbstractDSpaceRestReposit @Autowired private AuthorityUtils authorityUtils; - @Override - public HALResource wrapResource(AuthorityEntryRest model, String... rels) { - return new AuthorityEntryResource(model); - } - @PreAuthorize("hasAuthority('AUTHENTICATED')") public AuthorityEntryRest getResource(HttpServletRequest request, String name, String relId, - Pageable pageable, String projection) { + Pageable pageable, Projection projection) { Context context = obtainContext(); ChoiceAuthority choiceAuthority = cas.getChoiceAuthorityByAuthorityName(name); Choice choice = choiceAuthority.getChoice(null, relId, context.getCurrentLocale().toString()); if (choice == null) { throw new ResourceNotFoundException("The authority was not found"); } - return authorityUtils.convertEntry(choice, name); + return authorityUtils.convertEntry(choice, name, projection); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityRestRepository.java index 64dcf10468..27bc585f11 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorityRestRepository.java @@ -12,7 +12,7 @@ import java.util.List; import java.util.Set; import org.dspace.app.rest.model.AuthorityRest; -import org.dspace.app.rest.model.hateoas.AuthorityResource; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.AuthorityUtils; import org.dspace.content.authority.ChoiceAuthority; import org.dspace.content.authority.service.ChoiceAuthorityService; @@ -42,31 +42,25 @@ public class AuthorityRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { Set authoritiesName = cas.getChoiceAuthoritiesNames(); - List results = new ArrayList(); + List results = new ArrayList<>(); + Projection projection = utils.obtainProjection(true); for (String authorityName : authoritiesName) { ChoiceAuthority source = cas.getChoiceAuthorityByAuthorityName(authorityName); - AuthorityRest result = authorityUtils.convertAuthority(source, authorityName); + AuthorityRest result = authorityUtils.convertAuthority(source, authorityName, projection); results.add(result); } - return new PageImpl(results, pageable, results.size()); + return new PageImpl<>(results, pageable, results.size()); } @Override public Class getDomainClass() { return AuthorityRest.class; } - - @Override - public AuthorityResource wrapResource(AuthorityRest model, String... rels) { - return new AuthorityResource(model, utils, rels); - } - } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatRestRepository.java index f93e768943..9e9142cd63 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamFormatRestRepository.java @@ -15,11 +15,10 @@ import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import org.dspace.app.rest.converter.BitstreamFormatConverter; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.BitstreamFormatRest; -import org.dspace.app.rest.model.hateoas.BitstreamFormatResource; +import org.dspace.app.rest.projection.Projection; import org.dspace.authorize.AuthorizeException; import org.dspace.content.BitstreamFormat; import org.dspace.content.service.BitstreamFormatService; @@ -43,14 +42,6 @@ public class BitstreamFormatRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - List bit = null; try { - bit = bitstreamFormatService.findAll(context); + List bit = bitstreamFormatService.findAll(context); + return converter.toRestPage(utils.getPage(bit, pageable), utils.obtainProjection(true)); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = utils.getPage(bit, pageable).map(converter); - return page; } @Override @@ -100,7 +89,7 @@ public class BitstreamFormatRestRepository extends DSpaceRestRepository getDomainClass() { return BitstreamFormatRest.class; } - - @Override - public BitstreamFormatResource wrapResource(BitstreamFormatRest bs, String... rels) { - return new BitstreamFormatResource(bs, utils, rels); - } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java index 2af199cd98..1a23515f90 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java @@ -16,16 +16,20 @@ import java.util.List; import java.util.UUID; import javax.servlet.http.HttpServletRequest; -import org.dspace.app.rest.converter.BitstreamConverter; +import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.model.BitstreamRest; -import org.dspace.app.rest.model.hateoas.BitstreamResource; +import org.dspace.app.rest.model.BundleRest; import org.dspace.app.rest.model.patch.Patch; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.repository.patch.DSpaceObjectPatch; import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Community; 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.core.Context; @@ -49,6 +53,12 @@ public class BitstreamRestRepository extends DSpaceObjectRestRepository() { }); + public BitstreamRestRepository(BitstreamService dsoService) { + super(dsoService, new DSpaceObjectPatch() { }); this.bs = dsoService; } @@ -81,7 +90,7 @@ public class BitstreamRestRepository extends DSpaceObjectRestRepository page = new PageImpl(bit, pageable, total).map(dsoConverter); + Projection projection = utils.obtainProjection(true); + Page page = new PageImpl<>(bit, pageable, total) + .map((bitstream) -> converter.toRest(bitstream, projection)); return page; } @@ -115,11 +126,6 @@ public class BitstreamRestRepository extends DSpaceObjectRestRepository { - @Autowired - BrowseEntryConverter converter; + implements LinkRestRepository { @Autowired - BrowseIndexConverter bixConverter; + BrowseEntryConverter browseEntryConverter; @Autowired ScopeResolver scopeResolver; // FIXME It will be nice to drive arguments binding by annotation as in normal spring controller methods public Page listBrowseEntries(HttpServletRequest request, String browseName, - Pageable pageable, String projection) + Pageable pageable, Projection projection) throws BrowseException, SQLException { // FIXME this should be bind automatically and available as method // argument @@ -122,22 +117,13 @@ public class BrowseEntryLinkRepository extends AbstractDSpaceRestRepository BrowseInfo binfo = be.browse(bs); Pageable pageResultInfo = new PageRequest((binfo.getStart() - 1) / binfo.getResultsPerPage(), binfo.getResultsPerPage()); - Page page = new PageImpl(Arrays.asList(binfo.getStringResults()), pageResultInfo, - binfo.getTotal()).map(converter); - page.forEach(new Consumer() { - @Override - public void accept(BrowseEntryRest t) { - t.setBrowseIndex(bixConverter.convert(bi)); - } - }); + Page page = new PageImpl<>(Arrays.asList(binfo.getStringResults()), pageResultInfo, + binfo.getTotal()).map(browseEntryConverter); + BrowseIndexRest biRest = converter.toRest(bi, projection); + page.forEach(t -> t.setBrowseIndex(biRest)); return page; } - @Override - public BrowseEntryResource wrapResource(BrowseEntryRest entry, String... rels) { - return new BrowseEntryResource(entry); - } - @Override public boolean isEmbeddableRelation(Object data, String name) { BrowseIndexRest bir = (BrowseIndexRest) data; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java index f8b6bec7a0..83d230d503 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseIndexRestRepository.java @@ -7,18 +7,14 @@ */ package org.dspace.app.rest.repository; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import org.dspace.app.rest.converter.BrowseIndexConverter; import org.dspace.app.rest.model.BrowseIndexRest; -import org.dspace.app.rest.model.hateoas.BrowseIndexResource; import org.dspace.browse.BrowseException; import org.dspace.browse.BrowseIndex; import org.dspace.core.Context; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Component; @@ -29,8 +25,6 @@ import org.springframework.stereotype.Component; */ @Component(BrowseIndexRest.CATEGORY + "." + BrowseIndexRest.NAME) public class BrowseIndexRestRepository extends DSpaceRestRepository { - @Autowired - BrowseIndexConverter converter; @Override public BrowseIndexRest findOne(Context context, String name) { @@ -42,37 +36,23 @@ public class BrowseIndexRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - List it = null; - List indexesList = new ArrayList(); - int total = 0; try { - BrowseIndex[] indexes = BrowseIndex.getBrowseIndices(); - total = indexes.length; - for (BrowseIndex bix : indexes) { - indexesList.add(bix); - } + List indexes = Arrays.asList(BrowseIndex.getBrowseIndices()); + return converter.toRestPage(indexes, pageable, indexes.size(), utils.obtainProjection(true)); } catch (BrowseException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = new PageImpl(indexesList, pageable, total).map(converter); - return page; } @Override public Class getDomainClass() { return BrowseIndexRest.class; } - - @Override - public BrowseIndexResource wrapResource(BrowseIndexRest bix, String... rels) { - return new BrowseIndexResource(bix, utils, rels); - } - -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseItemLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseItemLinkRepository.java index 9308bfe14a..8692bd6804 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseItemLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BrowseItemLinkRepository.java @@ -11,14 +11,12 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; - import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; -import org.dspace.app.rest.converter.ItemConverter; import org.dspace.app.rest.model.BrowseIndexRest; import org.dspace.app.rest.model.ItemRest; -import org.dspace.app.rest.model.hateoas.ItemResource; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.ScopeResolver; import org.dspace.browse.BrowseEngine; import org.dspace.browse.BrowseException; @@ -32,7 +30,6 @@ import org.dspace.sort.SortException; import org.dspace.sort.SortOption; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -47,18 +44,13 @@ import org.springframework.stereotype.Component; */ @Component(BrowseIndexRest.CATEGORY + "." + BrowseIndexRest.NAME + "." + BrowseIndexRest.ITEMS) public class BrowseItemLinkRepository extends AbstractDSpaceRestRepository - implements LinkRestRepository { - @Autowired - ItemConverter converter; - - @Autowired - ItemRestRepository itemRestRepository; + implements LinkRestRepository { @Autowired ScopeResolver scopeResolver; public Page listBrowseItems(HttpServletRequest request, String browseName, Pageable pageable, - String projection) + Projection projection) throws BrowseException, SQLException { //FIXME these should be bind automatically and available as method arguments String scope = null; @@ -156,13 +148,8 @@ public class BrowseItemLinkRepository extends AbstractDSpaceRestRepository for (IndexableObject bb : binfo.getBrowseItemResults()) { tmpResult.add((Item) bb); } - Page page = new PageImpl(tmpResult, pageResultInfo, binfo.getTotal()).map(converter); - return page; - } - @Override - public ItemResource wrapResource(ItemRest item, String... rels) { - return itemRestRepository.wrapResource(item, rels); + return converter.toRestPage(tmpResult, pageResultInfo, binfo.getTotal(), projection); } @Override 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 new file mode 100644 index 0000000000..144c2d5daf --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleRestRepository.java @@ -0,0 +1,205 @@ +/** + * 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.io.IOException; +import java.io.InputStream; +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.BitstreamRest; +import org.dspace.app.rest.model.BundleRest; +import org.dspace.app.rest.model.patch.Patch; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.repository.patch.BundlePatch; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Bitstream; +import org.dspace.content.BitstreamFormat; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.content.service.BitstreamFormatService; +import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.BundleService; +import org.dspace.content.service.ItemService; +import org.dspace.core.Constants; +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.security.access.AccessDeniedException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * This is the repository responsible for managing the Bundle Rest object + * + * @author Jelle Pelgrims (jelle.pelgrims at atmire.com) + */ + +@Component(BundleRest.CATEGORY + "." + BundleRest.NAME) +public class BundleRestRepository extends DSpaceObjectRestRepository { + + private static final Logger log = LogManager.getLogger(); + + @Autowired + private BundleService bundleService; + + @Autowired + private AuthorizeService authorizeService; + + @Autowired + private ItemService itemService; + + @Autowired + private BitstreamService bitstreamService; + + @Autowired + private BitstreamFormatService bitstreamFormatService; + + public BundleRestRepository(BundleService dsoService, BundlePatch dsoPatch) { + super(dsoService, dsoPatch); + this.bundleService = dsoService; + } + + @PreAuthorize("hasPermission(#uuid, 'BUNDLE', 'READ')") + public BundleRest findOne(Context context, UUID uuid) { + Bundle bundle = null; + try { + bundle = bundleService.find(context, uuid); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + if (bundle == null) { + return null; + } + return converter.toRest(bundle, utils.obtainProjection()); + } + + public Page findAll(Context context, Pageable pageable) { + throw new RepositoryMethodNotImplementedException(BundleRest.NAME, "findAll"); + } + + /** + * Apply a patch operation to a bundle + * + * @param context The context + * @param request The http request + * @param apiCategory The API category e.g. "api" + * @param model The DSpace model e.g. "metadatafield" + * @param uuid The UUID of the bundle to perform patch operations on + * @param patch The JSON Patch (https://tools.ietf.org/html/rfc6902) operation + * @throws AuthorizeException + * @throws SQLException + */ + @Override + @PreAuthorize("hasPermission(#uuid, 'BUNDLE', 'WRITE')") + protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, UUID uuid, + Patch patch) throws AuthorizeException, SQLException { + patchDSpaceObject(apiCategory, model, uuid, patch); + } + + /** + * Method to upload a bitstream to a bundle. + * + * @param context The context + * @param bundle The bundle where the bitstream should be stored + * @param fileName The filename as it was uploaded + * @param fileInputStream The input stream used to create the bitstream + * @param properties The properties to be assigned to the bitstream + * @return The uploaded bitstream + */ + public BitstreamRest uploadBitstream(Context context, Bundle bundle, String fileName, InputStream fileInputStream, + String properties) { + Item item = null; + Bitstream bitstream = null; + try { + List items = bundle.getItems(); + if (!items.isEmpty()) { + item = items.get(0); + } + if (item != null && !(authorizeService.authorizeActionBoolean(context, item, Constants.WRITE) + && authorizeService.authorizeActionBoolean(context, item, Constants.ADD))) { + throw new AccessDeniedException("You do not have write rights to update the Bundle's item"); + } + bitstream = processBitstreamCreation(context, bundle, fileInputStream, properties, + fileName); + if (item != null) { + itemService.update(context, item); + } + bundleService.update(context, bundle); + context.commit(); + } catch (AuthorizeException | IOException | SQLException e) { + String message = "Something went wrong with trying to create the single bitstream for file with filename: " + + fileName + + " for item with uuid: " + bundle.getID() + " and possible properties: " + properties; + log.error(message, e); + throw new RuntimeException(message, e); + } + + return converter.toRest(bitstream, Projection.DEFAULT); + } + + /** + * Creates the bitstream based on the given parameters + * + * @param context The context + * @param bundle The bundle where the bitstream should be stored + * @param fileInputStream The input stream used to create the bitstream + * @param properties The properties to be assigned to the bitstream + * @param originalFilename The filename as it was uploaded + * @return The bitstream which has been created + */ + private Bitstream processBitstreamCreation(Context context, Bundle bundle, InputStream fileInputStream, + String properties, String originalFilename) + throws AuthorizeException, IOException, SQLException { + + Bitstream bitstream = null; + if (StringUtils.isNotBlank(properties)) { + ObjectMapper mapper = new ObjectMapper(); + BitstreamRest bitstreamRest = null; + try { + bitstreamRest = mapper.readValue(properties, BitstreamRest.class); + } catch (Exception e) { + throw new UnprocessableEntityException("The properties parameter was incorrect: " + properties); + } + bitstream = bitstreamService.create(context, bundle, fileInputStream); + if (bitstreamRest.getMetadata() != null) { + metadataConverter.setMetadata(context, bitstream, bitstreamRest.getMetadata()); + } + String name = bitstreamRest.getName(); + if (StringUtils.isNotBlank(name)) { + bitstream.setName(context, name); + } else { + bitstream.setName(context, originalFilename); + } + + } else { + bitstream = bitstreamService.create(context, bundle, fileInputStream); + bitstream.setName(context, originalFilename); + + } + BitstreamFormat bitstreamFormat = bitstreamFormatService.guessFormat(context, bitstream); + bitstreamService.setFormat(context, bitstream, bitstreamFormat); + bitstreamService.update(context, bitstream); + + return bitstream; + } + + public Class getDomainClass() { + return BundleRest.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClaimedTaskRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClaimedTaskRestRepository.java index ed4ff40cb7..f0e1beff3c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClaimedTaskRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClaimedTaskRestRepository.java @@ -11,7 +11,6 @@ import java.io.IOException; import java.sql.SQLException; import java.util.List; import java.util.UUID; - import javax.mail.MessagingException; import javax.servlet.http.HttpServletRequest; @@ -19,13 +18,11 @@ import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; -import org.dspace.app.rest.converter.ClaimedTaskConverter; import org.dspace.app.rest.exception.RESTAuthorizationException; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.ClaimedTaskRest; import org.dspace.app.rest.model.PoolTaskRest; -import org.dspace.app.rest.model.hateoas.ClaimedTaskResource; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.service.ItemService; @@ -71,9 +68,6 @@ public class ClaimedTaskRestRepository extends DSpaceRestRepository findByUser(@Parameter(value = "uuid", required = true) UUID userID, Pageable pageable) { //FIXME this should be secured with annotation but they are currently ignored by search methods - List tasks = null; try { Context context = obtainContext(); EPerson currentUser = context.getCurrentUser(); @@ -113,15 +106,14 @@ public class ClaimedTaskRestRepository extends DSpaceRestRepository tasks = claimedTaskService.findByEperson(context, ep); + return converter.toRestPage(utils.getPage(tasks, pageable), utils.obtainProjection(true)); } else { throw new RESTAuthorizationException("Only administrators can search for claimed tasks of other users"); } } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = utils.getPage(tasks, pageable).map(converter); - return page; } @Override @@ -129,11 +121,6 @@ public class ClaimedTaskRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { throw new RepositoryMethodNotImplementedException(ClaimedTaskRest.NAME, "findAll"); } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java index 839b0ca5e4..28a1bc6e5f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java @@ -9,7 +9,6 @@ package org.dspace.app.rest.repository; import java.io.IOException; import java.sql.SQLException; -import java.util.ArrayList; import java.util.List; import java.util.UUID; import javax.servlet.ServletInputStream; @@ -20,17 +19,14 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; -import org.dspace.app.rest.converter.BitstreamConverter; -import org.dspace.app.rest.converter.CollectionConverter; -import org.dspace.app.rest.converter.MetadataConverter; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.CommunityRest; -import org.dspace.app.rest.model.hateoas.CollectionResource; import org.dspace.app.rest.model.patch.Patch; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.repository.patch.DSpaceObjectPatch; import org.dspace.app.rest.utils.CollectionRestEqualityUtils; import org.dspace.authorize.AuthorizeException; @@ -44,7 +40,6 @@ import org.dspace.core.Constants; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.security.access.prepost.PreAuthorize; @@ -66,15 +61,6 @@ public class CollectionRestRepository extends DSpaceObjectRestRepository() {}); + public CollectionRestRepository(CollectionService dsoService) { + super(dsoService, new DSpaceObjectPatch() {}); + this.cs = dsoService; } @Override @@ -101,66 +87,47 @@ public class CollectionRestRepository extends DSpaceObjectRestRepository findAll(Context context, Pageable pageable) { - List it = null; - List collections = new ArrayList(); - int total = 0; try { - total = cs.countTotal(context); - it = cs.findAll(context, pageable.getPageSize(), pageable.getOffset()); - for (Collection c : it) { - collections.add(c); - } + long total = cs.countTotal(context); + List collections = cs.findAll(context, pageable.getPageSize(), pageable.getOffset()); + return converter.toRestPage(collections, pageable, total, utils.obtainProjection(true)); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = new PageImpl(collections, pageable, total).map(dsoConverter); - return page; } @SearchRestMethod(name = "findAuthorizedByCommunity") public Page findAuthorizedByCommunity( @Parameter(value = "uuid", required = true) UUID communityUuid, Pageable pageable) { - Context context = obtainContext(); - List it = null; - List collections = new ArrayList(); try { + Context context = obtainContext(); Community com = communityService.find(context, communityUuid); if (com == null) { throw new ResourceNotFoundException( CommunityRest.CATEGORY + "." + CommunityRest.NAME + " with id: " + communityUuid + " not found"); } - it = cs.findAuthorized(context, com, Constants.ADD); - for (Collection c : it) { - collections.add(c); - } + List collections = cs.findAuthorized(context, com, Constants.ADD); + return converter.toRestPage(utils.getPage(collections, pageable), utils.obtainProjection(true)); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = utils.getPage(collections, pageable).map(dsoConverter); - return page; } @SearchRestMethod(name = "findAuthorized") public Page findAuthorized(Pageable pageable) { - Context context = obtainContext(); - List it = null; - List collections = new ArrayList(); try { - it = cs.findAuthorizedOptimized(context, Constants.ADD); - for (Collection c : it) { - collections.add(c); - } + Context context = obtainContext(); + List collections = cs.findAuthorizedOptimized(context, Constants.ADD); + return converter.toRestPage(utils.getPage(collections, pageable), utils.obtainProjection(true)); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = utils.getPage(collections, pageable).map(dsoConverter); - return page; } @Override @@ -175,11 +142,6 @@ public class CollectionRestRepository extends DSpaceObjectRestRepository() {}); + public CommunityRestRepository(CommunityService dsoService) { + super(dsoService, new DSpaceObjectPatch() {}); + this.cs = dsoService; } @Override @@ -105,7 +90,7 @@ public class CommunityRestRepository extends DSpaceObjectRestRepository findAll(Context context, Pageable pageable) { - List it = null; - List communities = new ArrayList(); - int total = 0; try { - total = cs.countTotal(context); - it = cs.findAll(context, pageable.getPageSize(), pageable.getOffset()); - for (Community c : it) { - communities.add(c); - } + long total = cs.countTotal(context); + List communities = cs.findAll(context, pageable.getPageSize(), pageable.getOffset()); + return converter.toRestPage(communities, pageable, total, utils.obtainProjection(true)); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = new PageImpl(communities, pageable, total).map(dsoConverter); - return page; } // TODO: Add methods in dspace api to support pagination of top level // communities @SearchRestMethod(name = "top") public Page findAllTop(Pageable pageable) { - List topCommunities = null; try { - topCommunities = cs.findAllTop(obtainContext()); + List communities = cs.findAllTop(obtainContext()); + return converter.toRestPage(utils.getPage(communities, pageable), utils.obtainProjection(true)); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = utils.getPage(topCommunities, pageable).map(dsoConverter); - return page; } // TODO: add method in dspace api to support direct query for subcommunities @@ -198,19 +174,17 @@ public class CommunityRestRepository extends DSpaceObjectRestRepository findSubCommunities(@Parameter(value = "parent", required = true) UUID parentCommunity, Pageable pageable) { Context context = obtainContext(); - List subCommunities = new ArrayList(); try { Community community = cs.find(context, parentCommunity); if (community == null) { throw new ResourceNotFoundException( CommunityRest.CATEGORY + "." + CommunityRest.NAME + " with id: " + parentCommunity + " not found"); } - subCommunities = community.getSubcommunities(); + List subCommunities = community.getSubcommunities(); + return converter.toRestPage(utils.getPage(subCommunities, pageable), utils.obtainProjection(true)); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = utils.getPage(subCommunities, pageable).map(dsoConverter); - return page; } @Override @@ -225,11 +199,6 @@ public class CommunityRestRepository extends DSpaceObjectRestRepository dsoService; final DSpaceObjectPatch dsoPatch; - final DSpaceObjectConverter dsoConverter; @Autowired MetadataConverter metadataConverter; DSpaceObjectRestRepository(DSpaceObjectService dsoService, - DSpaceObjectConverter dsoConverter, DSpaceObjectPatch dsoPatch) { this.dsoService = dsoService; this.dsoPatch = dsoPatch; - this.dsoConverter = dsoConverter; } /** @@ -78,7 +75,7 @@ public abstract class DSpaceObjectRestRepository getDomainClass(); - /** - * Wrap the REST model in a REST HAL Resource - * - * @param model - * the rest model instance - * @param rels - * the HAL links - * @return the REST Resource - */ - public abstract DSpaceResource wrapResource(T model, String... rels); - /** * Create and return a new instance. Data are usually retrieved from the thread bound http request * @@ -596,4 +583,4 @@ public abstract class DSpaceRestRepository searchFilters, final Pageable page) { + final List searchFilters, final Pageable page, + final Projection projection) { Context context = obtainContext(); IndexableObject scopeObject = scopeResolver.resolveScope(context, dsoScope); DiscoveryConfiguration discoveryConfiguration = searchConfigurationService @@ -111,7 +113,7 @@ public class DiscoveryRestRepository extends AbstractDSpaceRestRepository { return discoverResultConverter .convert(context, query, dsoType, configuration, dsoScope, searchFilters, page, searchResult, - discoveryConfiguration); + discoveryConfiguration, projection); } public FacetConfigurationRest getFacetsConfiguration(final String dsoScope, final String configuration) { @@ -150,7 +152,8 @@ public class DiscoveryRestRepository extends AbstractDSpaceRestRepository { } FacetResultsRest facetResultsRest = discoverFacetResultsConverter.convert(context, facetName, prefix, query, - dsoType, dsoScope, searchFilters, searchResult, discoveryConfiguration, page); + dsoType, dsoScope, searchFilters, searchResult, discoveryConfiguration, page, + utils.obtainProjection()); return facetResultsRest; } @@ -176,7 +179,8 @@ public class DiscoveryRestRepository extends AbstractDSpaceRestRepository { } SearchResultsRest searchResultsRest = discoverFacetsConverter.convert(context, query, dsoType, - configuration, dsoScope, searchFilters, page, discoveryConfiguration, searchResult); + configuration, dsoScope, searchFilters, page, discoveryConfiguration, searchResult, + utils.obtainProjection()); return searchResultsRest; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java index 3da43b1751..3d4c40649d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java @@ -18,13 +18,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; -import org.dspace.app.rest.converter.EPersonConverter; -import org.dspace.app.rest.converter.MetadataConverter; -import org.dspace.app.rest.exception.RESTAuthorizationException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.EPersonRest; -import org.dspace.app.rest.model.hateoas.EPersonResource; import org.dspace.app.rest.model.patch.Patch; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.repository.patch.EPersonPatch; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; @@ -33,7 +30,6 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.service.EPersonService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -53,16 +49,12 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository findAll(Context context, Pageable pageable) { - List epersons = null; - int total = 0; try { - if (!authorizeService.isAdmin(context)) { - throw new RESTAuthorizationException( - "The EPerson collection endpoint is reserved to system administrators"); - } - total = es.countTotal(context); - epersons = es.findAll(context, EPerson.EMAIL, pageable.getPageSize(), pageable.getOffset()); + long total = es.countTotal(context); + List epersons = es.findAll(context, EPerson.EMAIL, pageable.getPageSize(), pageable.getOffset()); + return converter.toRestPage(epersons, pageable, total, utils.obtainProjection(true)); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = new PageImpl(epersons, pageable, total).map(dsoConverter); - return page; } /** @@ -147,17 +132,15 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository findByName(@Parameter(value = "q", required = true) String q, Pageable pageable) { - List epersons = null; - int total = 0; try { Context context = obtainContext(); - epersons = es.search(context, q, pageable.getOffset(), pageable.getOffset() + pageable.getPageSize()); - total = es.searchResultCount(context, q); + long total = es.searchResultCount(context, q); + List epersons = es.search(context, q, pageable.getOffset(), + pageable.getOffset() + pageable.getPageSize()); + return converter.toRestPage(epersons, pageable, total, utils.obtainProjection(true)); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = new PageImpl(epersons, pageable, total).map(dsoConverter); - return page; } /** @@ -166,8 +149,6 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository getDomainClass() { return EPersonRest.class; } - - @Override - public EPersonResource wrapResource(EPersonRest eperson, String... rels) { - return new EPersonResource(eperson, utils, rels); - } - } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRestRepository.java index 31c4e4392c..c20388ad0c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/EntityTypeRestRepository.java @@ -10,10 +10,7 @@ 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; @@ -32,37 +29,28 @@ public class EntityTypeRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - List entityTypeList = null; try { - entityTypeList = entityTypeService.findAll(context); + List entityTypes = entityTypeService.findAll(context); + return converter.toRestPage(utils.getPage(entityTypes, pageable), utils.obtainProjection(true)); } 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-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupRestRepository.java index 112453316c..e8f0ec9a54 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupRestRepository.java @@ -11,17 +11,15 @@ import java.io.IOException; import java.sql.SQLException; import java.util.List; import java.util.UUID; - import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.ObjectMapper; -import org.dspace.app.rest.converter.GroupConverter; import org.dspace.app.rest.converter.MetadataConverter; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.GroupRest; -import org.dspace.app.rest.model.hateoas.GroupResource; import org.dspace.app.rest.model.patch.Patch; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.repository.patch.DSpaceObjectPatch; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; @@ -29,7 +27,6 @@ import org.dspace.eperson.Group; import org.dspace.eperson.service.GroupService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -46,9 +43,8 @@ public class GroupRestRepository extends DSpaceObjectRestRepository() {}); + GroupRestRepository(GroupService dsoService) { + super(dsoService, new DSpaceObjectPatch() {}); this.gs = dsoService; } @@ -79,7 +75,7 @@ public class GroupRestRepository extends DSpaceObjectRestRepository findAll(Context context, Pageable pageable) { - List groups = null; - int total = 0; try { - total = gs.countTotal(context); - groups = gs.findAll(context, null, pageable.getPageSize(), pageable.getOffset()); + long total = gs.countTotal(context); + List groups = gs.findAll(context, null, pageable.getPageSize(), pageable.getOffset()); + return converter.toRestPage(groups, pageable, total, utils.obtainProjection(true)); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = new PageImpl(groups, pageable, total).map(dsoConverter); - return page; } @Override @@ -123,10 +116,4 @@ public class GroupRestRepository extends DSpaceObjectRestRepository getDomainClass() { return GroupRest.class; } - - @Override - public GroupResource wrapResource(GroupRest eperson, String... rels) { - return new GroupResource(eperson, utils, rels); - } - } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/HarvestedCollectionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/HarvestedCollectionRestRepository.java index 5cd11df986..ad153a5ddc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/HarvestedCollectionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/HarvestedCollectionRestRepository.java @@ -13,7 +13,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; - import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; @@ -22,6 +21,7 @@ import org.dspace.app.rest.converter.HarvestedCollectionConverter; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.HarvestTypeEnum; import org.dspace.app.rest.model.HarvestedCollectionRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.content.Collection; import org.dspace.core.Context; import org.dspace.harvest.HarvestedCollection; @@ -54,7 +54,8 @@ public class HarvestedCollectionRestRepository extends AbstractDSpaceRestReposit HarvestedCollection harvestedCollection = harvestedCollectionService.find(context, collection); List> configs = OAIHarvester.getAvailableMetadataFormats(); - return harvestedCollectionConverter.fromModel(harvestedCollection, collection, configs); + return harvestedCollectionConverter.fromModel(harvestedCollection, collection, configs, + utils.obtainProjection()); } /** @@ -75,7 +76,7 @@ public class HarvestedCollectionRestRepository extends AbstractDSpaceRestReposit if (harvestedCollectionRest.getHarvestType() == HarvestTypeEnum.NONE.getValue() && harvestedCollection != null) { harvestedCollectionService.delete(context, harvestedCollection); - return harvestedCollectionConverter.fromModel(null); + return harvestedCollectionConverter.convert(null, utils.obtainProjection()); } else if (harvestedCollectionRest.getHarvestType() != HarvestTypeEnum.NONE.getValue()) { List errors = testHarvestSettings(harvestedCollectionRest); @@ -89,7 +90,8 @@ public class HarvestedCollectionRestRepository extends AbstractDSpaceRestReposit harvestedCollection = harvestedCollectionService.find(context, collection); List> configs = OAIHarvester.getAvailableMetadataFormats(); - return harvestedCollectionConverter.fromModel(harvestedCollection, collection, configs); + return harvestedCollectionConverter.fromModel(harvestedCollection, collection, configs, + Projection.DEFAULT); } else { throw new UnprocessableEntityException( "Incorrect harvest settings in request. The following errors were found: " + errors.toString() @@ -147,7 +149,6 @@ public class HarvestedCollectionRestRepository extends AbstractDSpaceRestReposit /** * Function used to verify that the harvest settings work - * @param collection The collection to which the harvest settings should be aplied * @param harvestedCollectionRest A object containg the harvest settings to be tested * @return */ diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRelationshipLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRelationshipLinkRepository.java new file mode 100644 index 0000000000..4c76dc2274 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRelationshipLinkRepository.java @@ -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/ + */ +package org.dspace.app.rest.repository; + +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.model.RelationshipRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.Item; +import org.dspace.content.Relationship; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.RelationshipService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Component; + +/** + * Link repository for "relationships" subresource of an individual item. + */ +@Component(ItemRest.CATEGORY + "." + ItemRest.NAME + "." + ItemRest.RELATIONSHIPS) +public class ItemRelationshipLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + RelationshipService relationshipService; + + @Autowired + ItemService itemService; + + //@PreAuthorize("hasPermission(#itemId, 'ITEM', 'READ')") + public Page getItemRelationships(@Nullable HttpServletRequest request, + UUID itemId, + @Nullable Pageable optionalPageable, + Projection projection) { + try { + Context context = obtainContext(); + Item item = itemService.find(context, itemId); + if (item == null) { + return null; + } + Pageable pageable = optionalPageable != null ? optionalPageable : new PageRequest(0, 20); + Integer limit = pageable == null ? null : pageable.getPageSize(); + Integer offset = pageable == null ? null : pageable.getOffset(); + int total = relationshipService.countByItem(context, item); + List relationships = relationshipService.findByItem(context, item, limit, offset); + return converter.toRestPage(relationships, pageable, total, projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java index 51ca103fb0..4539e62b95 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java @@ -21,21 +21,23 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; -import org.dspace.app.rest.converter.ItemConverter; import org.dspace.app.rest.converter.MetadataConverter; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.BundleRest; import org.dspace.app.rest.model.ItemRest; -import org.dspace.app.rest.model.hateoas.ItemResource; import org.dspace.app.rest.model.patch.Patch; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.repository.patch.ItemPatch; import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.content.Relationship; import org.dspace.content.RelationshipType; import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.BundleService; import org.dspace.content.service.CollectionService; import org.dspace.content.service.InstallItemService; import org.dspace.content.service.ItemService; @@ -46,7 +48,6 @@ import org.dspace.core.Context; import org.dspace.util.UUIDUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.security.access.prepost.PreAuthorize; @@ -73,6 +74,9 @@ public class ItemRestRepository extends DSpaceObjectRestRepository findAll(Context context, Pageable pageable) { - Iterator it = null; - List items = new ArrayList(); - int total = 0; try { - total = itemService.countTotal(context); - it = itemService.findAll(context, pageable.getPageSize(), pageable.getOffset()); + long total = itemService.countTotal(context); + Iterator it = itemService.findAll(context, pageable.getPageSize(), pageable.getOffset()); + List items = new ArrayList<>(); while (it.hasNext()) { - Item i = it.next(); - items.add(i); + items.add(it.next()); } + return converter.toRestPage(items, pageable, total, utils.obtainProjection(true)); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = new PageImpl(items, pageable, total).map(dsoConverter); - return page; } @Override @@ -141,7 +139,7 @@ public class ItemRestRepository extends DSpaceObjectRestRepository 0) { + throw new DSpaceBadRequestException("The bundle name already exists in the item"); + } + + Bundle bundle = bundleService.create(context, item, bundleRest.getName()); + + metadataConverter.setMetadata(context, bundle, bundleRest.getMetadata()); + bundle.setName(context, bundleRest.getName()); + + context.commit(); + + return bundle; + + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/LicenseRestLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/LicenseRestLinkRepository.java index 7ec1700361..2d69877680 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/LicenseRestLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/LicenseRestLinkRepository.java @@ -13,8 +13,7 @@ import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.LicenseRest; -import org.dspace.app.rest.model.hateoas.HALResource; -import org.dspace.app.rest.model.hateoas.LicenseResource; +import org.dspace.app.rest.projection.Projection; import org.dspace.content.Collection; import org.dspace.content.service.CollectionService; import org.dspace.core.Context; @@ -31,7 +30,7 @@ import org.springframework.stereotype.Component; */ @Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME + "." + CollectionRest.LICENSE) public class LicenseRestLinkRepository extends AbstractDSpaceRestRepository - implements LinkRestRepository { + implements LinkRestRepository { @Autowired CollectionService collectionService; @@ -39,13 +38,9 @@ public class LicenseRestLinkRepository extends AbstractDSpaceRestRepository @Autowired LicenseService licenseService; - @Override - public HALResource wrapResource(LicenseRest model, String... rels) { - return new LicenseResource(model); - } - @PreAuthorize("hasAuthority('AUTHENTICATED')") - public LicenseRest getLicenseCollection(HttpServletRequest request, UUID uuid, Pageable pageable, String projection) + public LicenseRest getLicenseCollection(HttpServletRequest request, UUID uuid, Pageable pageable, + Projection projection) throws Exception { Context context = obtainContext(); Collection collection = collectionService.find(context, uuid); @@ -62,7 +57,8 @@ public class LicenseRestLinkRepository extends AbstractDSpaceRestRepository return licenseRest; } - public boolean isEmbeddableRelation(LicenseRest data, String name) { + @Override + public boolean isEmbeddableRelation(Object data, String name) { return false; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/LinkRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/LinkRestRepository.java index ddd3d3fb32..98cc08fd23 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/LinkRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/LinkRestRepository.java @@ -7,19 +7,13 @@ */ package org.dspace.app.rest.repository; -import java.io.Serializable; - -import org.dspace.app.rest.model.hateoas.HALResource; - /** * This is the interface for Link Repositories. * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -public interface LinkRestRepository { - public abstract HALResource wrapResource(L model, String... rels); - - public default boolean isEmbeddableRelation(Object data, String name) { +public interface LinkRestRepository { + default boolean isEmbeddableRelation(Object data, String name) { return true; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java index 923ae6aecb..b61737549f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java @@ -21,11 +21,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.Gson; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; -import org.dspace.app.rest.converter.MetadataFieldConverter; import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.MetadataFieldRest; -import org.dspace.app.rest.model.hateoas.MetadataFieldResource; +import org.dspace.app.rest.projection.Projection; import org.dspace.authorize.AuthorizeException; import org.dspace.content.MetadataField; import org.dspace.content.MetadataSchema; @@ -54,12 +53,6 @@ public class MetadataFieldRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - List metadataField = null; try { - metadataField = metadataFieldService.findAll(context); + List metadataFields = metadataFieldService.findAll(context); + return converter.toRestPage(utils.getPage(metadataFields, pageable), utils.obtainProjection(true)); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = utils.getPage(metadataField, pageable).map(converter); - return page; } @SearchRestMethod(name = "bySchema") public Page findBySchema(@Parameter(value = "schema", required = true) String schemaName, Pageable pageable) { - Context context = obtainContext(); - List metadataFields = null; try { + Context context = obtainContext(); MetadataSchema schema = metadataSchemaService.find(context, schemaName); if (schema == null) { return null; } - metadataFields = metadataFieldService.findAllInSchema(context, schema); + List metadataFields = metadataFieldService.findAllInSchema(context, schema); + return converter.toRestPage(utils.getPage(metadataFields, pageable), utils.obtainProjection(true)); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = utils.getPage(metadataFields, pageable).map(converter); - return page; } @Override @@ -109,11 +98,6 @@ public class MetadataFieldRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - List metadataSchema = null; try { - metadataSchema = metadataSchemaService.findAll(context); + List metadataSchemas = metadataSchemaService.findAll(context); + return converter.toRestPage(utils.getPage(metadataSchemas, pageable), utils.obtainProjection(true)); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = utils.getPage(metadataSchema, pageable).map(converter); - return page; } @Override @@ -83,11 +74,6 @@ public class MetadataSchemaRestRepository extends DSpaceRestRepository findByUser(@Parameter(value = "uuid") UUID userID, Pageable pageable) { - List tasks = null; try { Context context = obtainContext(); //FIXME this should be secured with annotation but they are currently ignored by search methods @@ -107,7 +100,8 @@ public class PoolTaskRestRepository extends DSpaceRestRepository tasks = poolTaskService.findByEperson(context, ep); + return converter.toRestPage(utils.getPage(tasks, pageable), utils.obtainProjection(true)); } else { throw new RESTAuthorizationException("Only administrators can search for pool tasks of other users"); } @@ -116,8 +110,6 @@ public class PoolTaskRestRepository extends DSpaceRestRepository page = utils.getPage(tasks, pageable).map(converter); - return page; } @Override @@ -125,11 +117,6 @@ public class PoolTaskRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { throw new RuntimeException("Method not allowed!"); } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessRestRepository.java new file mode 100644 index 0000000000..ebfdb4d2a6 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessRestRepository.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.app.rest.repository; + +import java.sql.SQLException; +import java.util.List; + +import org.apache.log4j.Logger; +import org.dspace.app.rest.model.ProcessRest; +import org.dspace.core.Context; +import org.dspace.scripts.Process; +import org.dspace.scripts.service.ProcessService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * The repository for the Process workload + */ +@Component(ProcessRest.CATEGORY + "." + ProcessRest.NAME) +public class ProcessRestRepository extends DSpaceRestRepository { + + private static final Logger log = Logger.getLogger(ProcessRestRepository.class); + + @Autowired + private ProcessService processService; + + @Override + @PreAuthorize("hasPermission(#id, 'PROCESS', 'READ')") + public ProcessRest findOne(Context context, Integer id) { + try { + Process process = processService.find(context, id); + if (process == null) { + return null; + } + return converter.toRest(process, utils.obtainProjection()); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return null; + } + + @Override + @PreAuthorize("hasAuthority('ADMIN')") + public Page findAll(Context context, Pageable pageable) { + try { + int total = processService.countTotal(context); + List processes = processService.findAll(context, pageable.getPageSize(), pageable.getOffset()); + return converter.toRestPage(processes, pageable, total, utils.obtainProjection(true)); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @Override + public Class getDomainClass() { + return ProcessRest.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java index a6af078b26..013bc20dd3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipRestRepository.java @@ -9,7 +9,6 @@ package org.dspace.app.rest.repository; import java.io.IOException; import java.sql.SQLException; -import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.UUID; @@ -20,13 +19,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.log4j.Logger; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; -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.app.rest.projection.Projection; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.DSpaceObject; @@ -42,7 +38,6 @@ import org.dspace.eperson.EPerson; import org.dspace.services.RequestService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.security.access.AccessDeniedException; @@ -70,50 +65,38 @@ public class RelationshipRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - int total = 0; - List relationships = new ArrayList<>(); try { - total = relationshipService.countTotal(context); - relationships = relationshipService.findAll(context, + long total = relationshipService.countTotal(context); + List relationships = relationshipService.findAll(context, pageable.getPageSize(), pageable.getOffset()); + return converter.toRestPage(relationships, pageable, total, utils.obtainProjection(true)); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = new PageImpl(relationships, - pageable, total).map(relationshipConverter); - return page; } + @Override 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 { @@ -133,7 +116,8 @@ public class RelationshipRestRepository extends DSpaceRestRepository page = new PageImpl(relationships, - pageable, total).map(relationshipConverter); - return page; - + return converter.toRestPage(relationships, pageable, total, utils.obtainProjection(true)); } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRestRepository.java index ff163555f6..9e88019fba 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RelationshipTypeRestRepository.java @@ -10,10 +10,7 @@ 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; @@ -31,33 +28,27 @@ public class RelationshipTypeRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - List relationshipTypeList = null; try { - relationshipTypeList = relationshipTypeService.findAll(context); + List relationshipTypes = relationshipTypeService.findAll(context); + return converter.toRestPage(utils.getPage(relationshipTypes, pageable), utils.obtainProjection(true)); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = utils.getPage(relationshipTypeList, pageable).map(relationshipTypeConverter); - return page; } + @Override public Class getDomainClass() { return RelationshipTypeRest.class; } - - public DSpaceResource wrapResource(RelationshipTypeRest model, String... rels) { - return new RelationshipTypeResource(model, utils, rels); - } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java index 004d7b72ab..0d0657dbbb 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ResourcePolicyRestRepository.java @@ -9,11 +9,8 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; -import org.dspace.app.rest.converter.ResourcePolicyConverter; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.model.ResourcePolicyRest; -import org.dspace.app.rest.model.hateoas.ResourcePolicyResource; -import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.core.Context; @@ -34,12 +31,6 @@ public class ResourcePolicyRestRepository extends DSpaceRestRepository getDomainClass() { return ResourcePolicyRest.class; } - - - @Override - public ResourcePolicyResource wrapResource(ResourcePolicyRest model, String... rels) { - return new ResourcePolicyResource(model, utils, rels); - } - } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java new file mode 100644 index 0000000000..7557aca95e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ScriptRestRepository.java @@ -0,0 +1,155 @@ +/** + * 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.io.IOException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.cli.ParseException; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.converter.DSpaceRunnableParameterConverter; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.model.ParameterValueRest; +import org.dspace.app.rest.model.ProcessRest; +import org.dspace.app.rest.model.ScriptRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.scripts.handler.impl.RestDSpaceRunnableHandler; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.dspace.scripts.DSpaceCommandLineParameter; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.scripts.service.ScriptService; +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 REST repository dealing with the Script logic + */ +@Component(ScriptRest.CATEGORY + "." + ScriptRest.NAME) +public class ScriptRestRepository extends DSpaceRestRepository { + + private static final Logger log = LogManager.getLogger(); + + @Autowired + private ScriptService scriptService; + + @Autowired + private DSpaceRunnableParameterConverter dSpaceRunnableParameterConverter; + + @Override + public ScriptRest findOne(Context context, String name) { + + DSpaceRunnable dSpaceRunnable = scriptService.getScriptForName(name); + if (dSpaceRunnable != null) { + if (dSpaceRunnable.isAllowedToExecute(context)) { + return converter.toRest(dSpaceRunnable, utils.obtainProjection()); + } else { + throw new AccessDeniedException("The current user was not authorized to access this script"); + } + } + throw new DSpaceBadRequestException("The script with name: " + name + " could not be found"); + } + + @Override + public Page findAll(Context context, Pageable pageable) { + List dSpaceRunnables = scriptService.getDSpaceRunnables(context); + return converter.toRestPage(utils.getPage(dSpaceRunnables, pageable), utils.obtainProjection(true)); + } + + @Override + public Class getDomainClass() { + return ScriptRest.class; + } + + /** + * This method will take a String scriptname parameter and it'll try to resolve this to a script known by DSpace. + * If a script is found, it'll start a process for this script with the given properties to this request + * @param scriptName The name of the script that will try to be resolved and started + * @return A ProcessRest object representing the started process for this script + * @throws SQLException If something goes wrong + * @throws IOException If something goes wrong + */ + public ProcessRest startProcess(String scriptName) throws SQLException, IOException, AuthorizeException { + Context context = obtainContext(); + String properties = requestService.getCurrentRequest().getServletRequest().getParameter("properties"); + List dSpaceCommandLineParameters = + processPropertiesToDSpaceCommandLineParameters(properties); + DSpaceRunnable scriptToExecute = scriptService.getScriptForName(scriptName); + if (scriptToExecute == null) { + throw new DSpaceBadRequestException("The script for name: " + scriptName + " wasn't found"); + } + if (!scriptToExecute.isAllowedToExecute(context)) { + throw new AuthorizeException("Current user is not eligible to execute script with name: " + scriptName); + } + RestDSpaceRunnableHandler restDSpaceRunnableHandler = new RestDSpaceRunnableHandler( + context.getCurrentUser(), scriptName, dSpaceCommandLineParameters); + List args = constructArgs(dSpaceCommandLineParameters); + try { + runDSpaceScript(scriptToExecute, restDSpaceRunnableHandler, args); + context.complete(); + return converter.toRest(restDSpaceRunnableHandler.getProcess(), Projection.DEFAULT); + } catch (SQLException e) { + log.error("Failed to create a process with user: " + context.getCurrentUser() + + " scriptname: " + scriptName + " and parameters " + DSpaceCommandLineParameter + .concatenate(dSpaceCommandLineParameters), e); + } + return null; + } + + private List processPropertiesToDSpaceCommandLineParameters(String propertiesJson) + throws IOException { + List parameterValueRestList = new LinkedList<>(); + ObjectMapper objectMapper = new ObjectMapper(); + if (StringUtils.isNotBlank(propertiesJson)) { + parameterValueRestList = Arrays.asList(objectMapper.readValue(propertiesJson, ParameterValueRest[].class)); + } + + List dSpaceCommandLineParameters = new LinkedList<>(); + dSpaceCommandLineParameters.addAll( + parameterValueRestList.stream().map(x -> dSpaceRunnableParameterConverter.toModel(x)) + .collect(Collectors.toList())); + return dSpaceCommandLineParameters; + } + + private List constructArgs(List dSpaceCommandLineParameters) { + List args = new ArrayList<>(); + for (DSpaceCommandLineParameter parameter : dSpaceCommandLineParameters) { + args.add(parameter.getName()); + if (parameter.getValue() != null) { + args.add(parameter.getValue()); + } + } + return args; + } + + private void runDSpaceScript(DSpaceRunnable scriptToExecute, + RestDSpaceRunnableHandler restDSpaceRunnableHandler, List args) { + try { + scriptToExecute.initialize(args.toArray(new String[0]), restDSpaceRunnableHandler); + restDSpaceRunnableHandler.schedule(scriptToExecute); + } catch (ParseException e) { + scriptToExecute.printHelp(); + restDSpaceRunnableHandler + .handleException( + "Failed to parse the arguments given to the script with name: " + scriptToExecute.getName() + + " and args: " + args, e); + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SiteRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SiteRestRepository.java index e374df7fca..fcc2eb74e0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SiteRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SiteRestRepository.java @@ -8,14 +8,12 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.UUID; import javax.servlet.http.HttpServletRequest; -import org.dspace.app.rest.converter.SiteConverter; import org.dspace.app.rest.model.SiteRest; -import org.dspace.app.rest.model.hateoas.SiteResource; import org.dspace.app.rest.model.patch.Patch; import org.dspace.app.rest.repository.patch.DSpaceObjectPatch; import org.dspace.authorize.AuthorizeException; @@ -24,7 +22,6 @@ import org.dspace.content.service.SiteService; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -41,9 +38,8 @@ public class SiteRestRepository extends DSpaceObjectRestRepository() {}); + public SiteRestRepository(SiteService dsoService) { + super(dsoService, new DSpaceObjectPatch() {}); this.sitesv = dsoService; } @@ -58,20 +54,17 @@ public class SiteRestRepository extends DSpaceObjectRestRepository findAll(Context context, Pageable pageable) { - List sites = new ArrayList(); - int total = 1; try { - sites.add(sitesv.findSite(context)); + List sites = Arrays.asList(sitesv.findSite(context)); + return converter.toRestPage(sites, pageable, 1L, utils.obtainProjection(true)); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = new PageImpl(sites, pageable, total).map(dsoConverter); - return page; } @Override @@ -85,10 +78,4 @@ public class SiteRestRepository extends DSpaceObjectRestRepository getDomainClass() { return SiteRest.class; } - - @Override - public SiteResource wrapResource(SiteRest site, String... rels) { - return new SiteResource(site, utils, rels); - } - -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/StatisticsRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/StatisticsRestRepository.java index 8f7d2b8a44..0838b65d18 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/StatisticsRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/StatisticsRestRepository.java @@ -13,7 +13,6 @@ import org.springframework.stereotype.Component; @Component(StatisticsSupportRest.CATEGORY + "." + StatisticsSupportRest.NAME) public class StatisticsRestRepository extends AbstractDSpaceRestRepository { - public StatisticsSupportRest getStatisticsSupport() { return new StatisticsSupportRest(); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionDefinitionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionDefinitionRestRepository.java index 27f6913289..c4ea1bc866 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionDefinitionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionDefinitionRestRepository.java @@ -8,15 +8,12 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; -import java.util.ArrayList; 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.converter.SubmissionDefinitionConverter; import org.dspace.app.rest.model.SubmissionDefinitionRest; -import org.dspace.app.rest.model.hateoas.SubmissionDefinitionResource; import org.dspace.app.util.SubmissionConfig; import org.dspace.app.util.SubmissionConfigReader; import org.dspace.app.util.SubmissionConfigReaderException; @@ -24,9 +21,7 @@ import org.dspace.content.Collection; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.CollectionService; import org.dspace.core.Context; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -42,9 +37,6 @@ public class SubmissionDefinitionRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - List subConfs = new ArrayList(); int total = submissionConfigReader.countSubmissionConfigs(); - subConfs = submissionConfigReader.getAllSubmissionConfigs(pageable.getPageSize(), pageable.getOffset()); - Page page = new PageImpl(subConfs, pageable, total).map(converter); - return page; + List subConfs = submissionConfigReader.getAllSubmissionConfigs( + pageable.getPageSize(), pageable.getOffset()); + return converter.toRestPage(subConfs, pageable, total, utils.obtainProjection(true)); } @PreAuthorize("hasAuthority('AUTHENTICATED')") @@ -78,7 +69,8 @@ public class SubmissionDefinitionRestRepository extends DSpaceRestRepository getDomainClass() { return SubmissionDefinitionRest.class; } - - @Override - public SubmissionDefinitionResource wrapResource(SubmissionDefinitionRest sd, String... rels) { - return new SubmissionDefinitionResource(sd, utils, rels); - } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java index 5bbc05b882..51b0075e17 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionFormRestRepository.java @@ -7,19 +7,14 @@ */ package org.dspace.app.rest.repository; -import java.util.ArrayList; import java.util.List; -import org.dspace.app.rest.converter.SubmissionFormConverter; import org.dspace.app.rest.model.SubmissionFormRest; -import org.dspace.app.rest.model.hateoas.SubmissionFormResource; import org.dspace.app.util.DCInputSet; import org.dspace.app.util.DCInputsReader; import org.dspace.app.util.DCInputsReaderException; import org.dspace.core.Context; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -31,13 +26,10 @@ import org.springframework.stereotype.Component; */ @Component(SubmissionFormRest.CATEGORY + "." + SubmissionFormRest.NAME) public class SubmissionFormRestRepository extends DSpaceRestRepository - implements LinkRestRepository { + implements LinkRestRepository { private DCInputsReader inputReader; - @Autowired - private SubmissionFormConverter converter; - public SubmissionFormRestRepository() throws DCInputsReaderException { inputReader = new DCInputsReader(); } @@ -54,30 +46,23 @@ public class SubmissionFormRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - List subConfs = new ArrayList(); - int total = inputReader.countInputs(); try { - subConfs = inputReader.getAllInputs(pageable.getPageSize(), pageable.getOffset()); + long total = inputReader.countInputs(); + List subConfs = inputReader.getAllInputs(pageable.getPageSize(), pageable.getOffset()); + return converter.toRestPage(subConfs, pageable, total, utils.obtainProjection(true)); } catch (DCInputsReaderException e) { throw new IllegalStateException(e.getMessage(), e); } - Page page = new PageImpl(subConfs, pageable, total).map(converter); - return page; } @Override public Class getDomainClass() { return SubmissionFormRest.class; } - - @Override - public SubmissionFormResource wrapResource(SubmissionFormRest sd, String... rels) { - return new SubmissionFormResource(sd, utils, rels); - } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionPanelRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionPanelRestRepository.java index 125884af49..b9acb2ea32 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionPanelRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionPanelRestRepository.java @@ -10,18 +10,14 @@ package org.dspace.app.rest.repository; import java.util.ArrayList; import java.util.List; -import org.dspace.app.rest.converter.SubmissionSectionConverter; import org.dspace.app.rest.model.SubmissionDefinitionRest; import org.dspace.app.rest.model.SubmissionSectionRest; -import org.dspace.app.rest.model.hateoas.SubmissionSectionResource; import org.dspace.app.util.SubmissionConfig; import org.dspace.app.util.SubmissionConfigReader; import org.dspace.app.util.SubmissionConfigReaderException; import org.dspace.app.util.SubmissionStepConfig; import org.dspace.core.Context; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -36,9 +32,6 @@ public class SubmissionPanelRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - List subConfs = new ArrayList(); - subConfs = submissionConfigReader.getAllSubmissionConfigs(pageable.getPageSize(), pageable.getOffset()); - int total = 0; + List subConfs = submissionConfigReader.getAllSubmissionConfigs( + pageable.getPageSize(), pageable.getOffset()); + long total = 0; List stepConfs = new ArrayList<>(); for (SubmissionConfig config : subConfs) { total = +config.getNumberOfSteps(); @@ -69,19 +62,11 @@ public class SubmissionPanelRestRepository extends DSpaceRestRepository page = new PageImpl(stepConfs, pageable, total) - .map(converter); - return page; + return converter.toRestPage(stepConfs, pageable, total, utils.obtainProjection(true)); } @Override public Class getDomainClass() { return SubmissionSectionRest.class; } - - @Override - public SubmissionSectionResource wrapResource(SubmissionSectionRest model, String... rels) { - return new SubmissionSectionResource(model, utils, rels); - } - -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionUploadRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionUploadRestRepository.java index 69fb7a64fc..db3672aa4a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionUploadRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionUploadRestRepository.java @@ -14,7 +14,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.model.AccessConditionOptionRest; import org.dspace.app.rest.model.SubmissionUploadRest; -import org.dspace.app.rest.model.hateoas.SubmissionUploadResource; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.DateMathParser; import org.dspace.app.util.SubmissionConfig; import org.dspace.app.util.SubmissionConfigReader; @@ -41,7 +41,7 @@ import org.springframework.stereotype.Component; */ @Component(SubmissionUploadRest.CATEGORY + "." + SubmissionUploadRest.NAME) public class SubmissionUploadRestRepository extends DSpaceRestRepository - implements LinkRestRepository { + implements LinkRestRepository { private static final Logger log = org.apache.logging.log4j.LogManager .getLogger(SubmissionUploadRestRepository.class); @@ -68,7 +68,7 @@ public class SubmissionUploadRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { List subConfs = new ArrayList(); subConfs = submissionConfigReader.getAllSubmissionConfigs(pageable.getPageSize(), pageable.getOffset()); + Projection projection = utils.obtainProjection(true); List results = new ArrayList<>(); for (SubmissionConfig config : subConfs) { for (int i = 0; i < config.getNumberOfSteps(); i++) { @@ -88,7 +89,7 @@ public class SubmissionUploadRestRepository extends DSpaceRestRepository wfs; - private SubmissionConfigReader submissionConfigReader; + private final SubmissionConfigReader submissionConfigReader; public WorkflowItemRestRepository() throws SubmissionConfigReaderException { submissionConfigReader = new SubmissionConfigReader(); @@ -108,39 +107,34 @@ public class WorkflowItemRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - List witems = null; - int total = 0; try { - total = wis.countAll(context); - witems = wis.findAll(context, pageable.getPageNumber(), pageable.getPageSize()); + long total = wis.countAll(context); + List witems = wis.findAll(context, pageable.getPageNumber(), pageable.getPageSize()); + return converter.toRestPage(witems, pageable, total, utils.obtainProjection(true)); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = new PageImpl(witems, pageable, total).map(converter); - return page; } @SearchRestMethod(name = "findBySubmitter") @PreAuthorize("hasAuthority('ADMIN')") public Page findBySubmitter(@Parameter(value = "uuid") UUID submitterID, Pageable pageable) { - List witems = null; - int total = 0; try { Context context = obtainContext(); EPerson ep = epersonService.find(context, submitterID); - witems = wis.findBySubmitter(context, ep, pageable.getPageNumber(), pageable.getPageSize()); - total = wis.countBySubmitter(context, ep); + long total = wis.countBySubmitter(context, ep); + List witems = wis.findBySubmitter(context, ep, pageable.getPageNumber(), + pageable.getPageSize()); + return converter.toRestPage(witems, pageable, total, utils.obtainProjection(true)); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = new PageImpl(witems, pageable, total).map(converter); - return page; } @Override @@ -163,7 +157,7 @@ public class WorkflowItemRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - List witems = null; - int total = 0; try { - total = wis.countTotal(context); - witems = wis.findAll(context, pageable.getPageSize(), pageable.getOffset()); + long total = wis.countTotal(context); + List witems = wis.findAll(context, pageable.getPageSize(), pageable.getOffset()); + return converter.toRestPage(witems, pageable, total, utils.obtainProjection(true)); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = new PageImpl(witems, pageable, total).map(converter); - return page; } //TODO @PreAuthorize("hasPermission(#submitterID, 'EPERSON', 'READ')") @SearchRestMethod(name = "findBySubmitter") public Page findBySubmitter(@Parameter(value = "uuid", required = true) UUID submitterID, Pageable pageable) { - List witems = null; - int total = 0; try { Context context = obtainContext(); EPerson ep = epersonService.find(context, submitterID); - witems = wis.findByEPerson(context, ep, pageable.getPageSize(), pageable.getOffset()); - total = wis.countByEPerson(context, ep); + long total = wis.countByEPerson(context, ep); + List witems = wis.findByEPerson(context, ep, pageable.getPageSize(), pageable.getOffset()); + return converter.toRestPage(witems, pageable, total, utils.obtainProjection(true)); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = new PageImpl(witems, pageable, total).map(converter); - return page; } @Override protected WorkspaceItemRest createAndReturn(Context context) throws SQLException, AuthorizeException { WorkspaceItem source = submissionService.createWorkspaceItem(context, getRequestService().getCurrentRequest()); - return converter.convert(source); + return converter.toRest(source, Projection.DEFAULT); } @Override protected WorkspaceItemRest save(Context context, WorkspaceItemRest wsi) { SubmissionConfig submissionConfig = submissionConfigReader .getSubmissionConfigByName(submissionConfigReader.getDefaultSubmissionConfigName()); - WorkspaceItem source = converter.toModel(wsi); + WorkspaceItem source = workspaceItemConverter.toModel(wsi); for (int stepNum = 0; stepNum < submissionConfig.getNumberOfSteps(); stepNum++) { SubmissionStepConfig stepConfig = submissionConfig.getStep(stepNum); @@ -197,7 +194,6 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository!"); } - } catch (Exception e) { log.error(e.getMessage(), e); } @@ -211,11 +207,6 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository { + + @Autowired + BundleOperationFactory patchFactory; + + /** + * Performs the move operation. + * @param restModel the rest representation of the bundle + * @param operation the move operation + * @throws UnprocessableEntityException + * @throws DSpaceBadRequestException + */ + 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..a712f3a224 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/factories/BundleOperationFactory.java @@ -0,0 +1,31 @@ +/** + * 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.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; + +/** + * Provides factory methods for obtaining instances of bundle patch operations. + */ +@Component +public class BundleOperationFactory { + + @Autowired + BundleMoveOperation bundleMoveOperation; + + /** + * Returns the patch instance for the move operation + */ + 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..09ee1b3c51 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/BundleMoveOperation.java @@ -0,0 +1,120 @@ +/** + * 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.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; + +/** + * This is the implementation for Bundle move patches. + * This operation moves bitstreams within a bundle from one index to another. + * + * Example: + * curl -X PATCH http://${dspace.url}/api/bundles/<:id-bundle> -H " + * Content-Type: application/json" -d '[{ "op": "move", "path": " + * /_links/bitstreams/1/href", "from": "/_links/bitstreams/0/href"]' + * + */ +@Component +public class BundleMoveOperation extends MovePatchOperation { + + @Autowired + BundleService bundleService; + + @Autowired + RequestService requestService; + + /** + * Executes the move patch operation. + * + * @param resource the rest model. + * @param operation the move patch operation. + * @return the updated rest model. + * @throws DSpaceBadRequestException + */ + @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 bitstreams 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.updateBitstreamOrder(context, bundle, from, to); + } catch (SQLException | AuthorizeException e) { + throw new DSpaceBadRequestException(e.getMessage(), e); + } + + return resource; + } + + /** + * This method should return the typed array to be used in the + * LateObjectEvaluator evaluation of json arrays. + * + * @return + */ + @Override + protected Class getArrayClassForEvaluation() { + return Integer[].class; + } + + /** + * This method should return the object type to be used in the + * LateObjectEvaluator evaluation of json objects. + * + * @return + */ + @Override + protected Class getClassForEvaluation() { + return Integer.class; + } + + /** + * Create an exception message for the move operation + * + * @param bundle The bundle we're performing a move operation on + * @param from The "from" location + * @param to The "to" location + * @param message A message to add after the prefix + * @return The created message + */ + 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..bc56faec2c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/patch/factories/impl/MovePatchOperation.java @@ -0,0 +1,106 @@ +/** + * 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.patch.factories.impl; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.UnprocessableEntityException; +import org.dspace.app.rest.model.RestModel; +import org.dspace.app.rest.model.patch.MoveOperation; +import org.dspace.app.rest.model.patch.Operation; + +/** + * Base class for move patch operations. + */ +public abstract class MovePatchOperation extends PatchOperation { + + /** + * The index to move the object from + */ + protected int from; + + /** + * The index to move the object to + */ + protected int to; + + /** + * Implements the patch operation for move operations. + * Before performing the move operation this method checks + * if the arguments provided are valid + * + * @param resource the rest model. + * @param operation the move patch operation. + * @return the updated rest model. + * @throws DSpaceBadRequestException + * @throws UnprocessableEntityException + */ + @Override + public R perform(R resource, Operation operation) { + checkMoveOperation(operation); + return move(resource, operation); + } + + /** + * Executes the move patch operation. + * + * @param resource the rest model. + * @param operation the move patch operation. + * @return the updated rest model. + * @throws DSpaceBadRequestException + * @throws UnprocessableEntityException + */ + abstract R move(R resource, Operation operation); + + /** + * This method checks if the operation contains any invalid arguments. Invalid arguments include: + * - from and path point to the same index + * - either of the indexes are negative + * @param operation the move patch 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); + } + } + + /** + * Fetches and returns the index from a path argument + * @param path the provided path (e.g. "/_links/bitstreams/1/href") + */ + 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); + } +} + diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java new file mode 100644 index 0000000000..6fd1aa10fc --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/scripts/handler/impl/RestDSpaceRunnableHandler.java @@ -0,0 +1,218 @@ +/** + * 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.scripts.handler.impl; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.sql.SQLException; +import java.util.List; + +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Options; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.logging.log4j.Logger; +import org.dspace.content.ProcessStatus; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.scripts.DSpaceCommandLineParameter; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.scripts.Process; +import org.dspace.scripts.factory.ScriptServiceFactory; +import org.dspace.scripts.handler.DSpaceRunnableHandler; +import org.dspace.scripts.service.ProcessService; + +/** + * The {@link DSpaceRunnableHandler} dealing with Scripts started from the REST api + */ +public class RestDSpaceRunnableHandler implements DSpaceRunnableHandler { + private static final Logger log = org.apache.logging.log4j.LogManager + .getLogger(RestDSpaceRunnableHandler.class); + + private ProcessService processService = ScriptServiceFactory.getInstance().getProcessService(); + + private Integer processId; + private String scriptName; + + /** + * This constructor will initialise the handler with the process created from the parameters + * @param ePerson The eperson that creates the process + * @param scriptName The name of the script for which is a process will be created + * @param parameters The parameters for this process + */ + public RestDSpaceRunnableHandler(EPerson ePerson, String scriptName, List parameters) { + Context context = new Context(); + try { + Process process = processService.create(context, ePerson, scriptName, parameters); + processId = process.getID(); + this.scriptName = process.getName(); + + context.complete(); + } catch (SQLException e) { + log.error("RestDSpaceRunnableHandler with ePerson: " + ePerson + .getEmail() + " for Script with name: " + scriptName + + " and parameters: " + parameters + " could nto be created", e); + } finally { + if (context.isValid()) { + context.abort(); + } + } + } + + @Override + public void start() { + Context context = new Context(); + try { + Process process = processService.find(context, processId); + processService.start(context, process); + context.complete(); + logInfo("The script has started"); + } catch (SQLException e) { + log.error("RestDSpaceRunnableHandler with process: " + processId + " could not be started", e); + } finally { + if (context.isValid()) { + context.abort(); + } + } + } + + @Override + public void handleCompletion() { + Context context = new Context(); + try { + Process process = processService.find(context, processId); + processService.complete(context, process); + context.complete(); + logInfo("The script has completed"); + } catch (SQLException e) { + log.error("RestDSpaceRunnableHandler with process: " + processId + " could not be completed", e); + } finally { + if (context.isValid()) { + context.abort(); + } + } + } + + @Override + public void handleException(Exception e) { + handleException(null, e); + } + + @Override + public void handleException(String message) { + handleException(message, null); + } + + @Override + public void handleException(String message, Exception e) { + if (message != null) { + logError(message); + } + if (e != null) { + logError(ExceptionUtils.getStackTrace(e)); + } + + Context context = new Context(); + try { + Process process = processService.find(context, processId); + processService.fail(context, process); + context.complete(); + } catch (SQLException sqlException) { + log.error("SQL exception while handling another exception", e); + } finally { + if (context.isValid()) { + context.abort(); + } + } + } + + @Override + public void logDebug(String message) { + String logMessage = getLogMessage(message); + log.debug(logMessage); + } + + private String getLogMessage(String message) { + return String + .format("Process id: %d, script name: %s, message: %s", processId, scriptName, message); + } + + @Override + public void logInfo(String message) { + String logMessage = getLogMessage(message); + log.info(logMessage); + + } + + @Override + public void logWarning(String message) { + String logMessage = getLogMessage(message); + log.warn(logMessage); + } + + @Override + public void logError(String message) { + String logMessage = getLogMessage(message); + log.error(logMessage); + } + + @Override + public void printHelp(Options options, String name) { + if (options != null) { + HelpFormatter formatter = new HelpFormatter(); + StringWriter out = new StringWriter(); + PrintWriter pw = new PrintWriter(out); + + formatter.printUsage(pw, 1000, name, options); + pw.flush(); + + String helpString = out.toString(); + + logInfo(helpString); + } + } + + /** + * This method will return the process created by this handler + * @return The Process database object created by this handler + */ + public Process getProcess() { + Context context = new Context(); + try { + return processService.find(context, processId); + } catch (SQLException e) { + log.error("RestDSpaceRunnableHandler with process: " + processId + " could not be found", e); + } finally { + if (context.isValid()) { + context.abort(); + } + } + return null; + } + + /** + * This method will schedule a process to be run, it will trigger the run method for the Script passed along + * to this method as well as updating the database logic for the Process representing the execution of this script + * @param script The script to be ran + */ + public void schedule(DSpaceRunnable script) { + Context context = new Context(); + try { + Process process = processService.find(context, processId); + process.setProcessStatus(ProcessStatus.SCHEDULED); + processService.update(context, process); + context.complete(); + } catch (SQLException e) { + log.error("RestDSpaceRunnableHandler with process: " + processId + " ran into an SQLException", e); + } finally { + if (context.isValid()) { + context.abort(); + } + } + script.run(); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ProcessRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ProcessRestPermissionEvaluatorPlugin.java new file mode 100644 index 0000000000..aee8bab8c2 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/ProcessRestPermissionEvaluatorPlugin.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.app.rest.security; + +import java.io.Serializable; +import java.sql.SQLException; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.model.ProcessRest; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.core.Context; +import org.dspace.scripts.Process; +import org.dspace.scripts.service.ProcessService; +import org.dspace.services.RequestService; +import org.dspace.services.model.Request; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +/** + * This class will handle calls made to Process endpoints. It will return false if the current user is null or + * if the current user is not the eperson that's attached to the process and that user isn't an admin. + * This class is an implementation for {@link RestObjectPermissionEvaluatorPlugin} for Process endpoints. + */ +@Component +public class ProcessRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { + + private static final Logger log = LoggerFactory.getLogger(ProcessRestPermissionEvaluatorPlugin.class); + + @Autowired + private RequestService requestService; + + @Autowired + private ProcessService processService; + + @Autowired + private AuthorizeService authorizeService; + + @Override + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, + DSpaceRestPermission restPermission) { + + if (!StringUtils.equalsIgnoreCase(targetType, ProcessRest.NAME)) { + return false; + } + + Request request = requestService.getCurrentRequest(); + Context context = ContextUtil.obtainContext(request.getServletRequest()); + + try { + int processId = Integer.parseInt(targetId.toString()); + Process process = processService.find(context, processId); + if (process == null) { + return true; + } + if (!((context.getCurrentUser() == null) || (!context.getCurrentUser().equals(process.getEPerson()) + && !authorizeService.isAdmin(context)))) { + return true; + } + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + return false; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/SubmissionService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/SubmissionService.java index eecf0f5424..04da631083 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/SubmissionService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/SubmissionService.java @@ -12,15 +12,12 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.UUID; - import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.atteo.evo.inflector.English; -import org.dspace.app.rest.converter.BitstreamFormatConverter; -import org.dspace.app.rest.converter.ResourcePolicyConverter; -import org.dspace.app.rest.converter.WorkspaceItemConverter; +import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.RESTAuthorizationException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.BitstreamRest; @@ -29,6 +26,7 @@ import org.dspace.app.rest.model.MetadataValueRest; import org.dspace.app.rest.model.ResourcePolicyRest; import org.dspace.app.rest.model.WorkspaceItemRest; import org.dspace.app.rest.model.step.UploadBitstreamRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.utils.ContextUtil; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.ResourcePolicy; @@ -77,12 +75,8 @@ public class SubmissionService { protected WorkflowService workflowService; @Autowired private RequestService requestService; - @Autowired(required = true) - BitstreamFormatConverter bfConverter; @Autowired - WorkspaceItemConverter workspaceItemConverter; - @Autowired(required = true) - ResourcePolicyConverter aCConverter; + private ConverterService converter; /** * Create a workspaceitem using the information in the reqest @@ -170,11 +164,11 @@ public class SubmissionService { } HttpServletRequest request = requestService.getCurrentRequest().getHttpServletRequest(); - data.setFormat(bfConverter.convert(source.getFormat(ContextUtil.obtainContext(request)))); + data.setFormat(converter.toRest(source.getFormat(ContextUtil.obtainContext(request)), Projection.DEFAULT)); for (ResourcePolicy rp : source.getResourcePolicies()) { if (ResourcePolicy.TYPE_CUSTOM.equals(rp.getRpType())) { - ResourcePolicyRest resourcePolicyRest = aCConverter.convert(rp); + ResourcePolicyRest resourcePolicyRest = converter.toRest(rp, Projection.DEFAULT); data.getAccessConditions().add(resourcePolicyRest); } } @@ -225,7 +219,8 @@ public class SubmissionService { if (wsi == null) { throw new UnprocessableEntityException("Workspace item is not found"); } - if (!workspaceItemConverter.convert(wsi).getErrors().isEmpty()) { + WorkspaceItemRest wsiRest = converter.toRest(wsi, Projection.DEFAULT); + if (!wsiRest.getErrors().isEmpty()) { throw new UnprocessableEntityException( "Start workflow failed due to validation error on workspaceitem"); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java index ac2b3844d8..97be32ecf9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/AuthorityUtils.java @@ -7,10 +7,10 @@ */ package org.dspace.app.rest.utils; -import org.dspace.app.rest.converter.AuthorityEntryRestConverter; -import org.dspace.app.rest.converter.AuthorityRestConverter; +import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.model.AuthorityEntryRest; import org.dspace.app.rest.model.AuthorityRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.content.authority.Choice; import org.dspace.content.authority.ChoiceAuthority; import org.dspace.content.authority.service.ChoiceAuthorityService; @@ -35,10 +35,7 @@ public class AuthorityUtils { private ChoiceAuthorityService cas; @Autowired - private AuthorityEntryRestConverter entryConverter; - - @Autowired - private AuthorityRestConverter authorityConverter; + private ConverterService converter; public boolean isChoice(String schema, String element, String qualifier) { @@ -62,10 +59,11 @@ public class AuthorityUtils { * * @param choice * @param authorityName + * @param projection the name of the projection to use, or {@code null}. * @return */ - public AuthorityEntryRest convertEntry(Choice choice, String authorityName) { - AuthorityEntryRest entry = entryConverter.convert(choice); + public AuthorityEntryRest convertEntry(Choice choice, String authorityName, Projection projection) { + AuthorityEntryRest entry = converter.toRest(choice, projection); entry.setAuthorityName(authorityName); return entry; } @@ -74,11 +72,12 @@ public class AuthorityUtils { * TODO the authorityName MUST be a part of ChoiceAuthority model * * @param source - * @param name + * @param authorityName + * @param projection the projecton to use. * @return */ - public AuthorityRest convertAuthority(ChoiceAuthority source, String authorityName) { - AuthorityRest result = authorityConverter.convert(source); + public AuthorityRest convertAuthority(ChoiceAuthority source, String authorityName, Projection projection) { + AuthorityRest result = converter.toRest(source, projection); result.setName(authorityName); return result; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.java new file mode 100644 index 0000000000..8e8511915e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/RegexUtils.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.app.rest.utils; + +public class RegexUtils { + + private RegexUtils(){} + + /** + * Regular expression in the request mapping to accept UUID as identifier + */ + public static final String REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID = + "/{uuid:[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}}"; + + /** + * Regular expression in the request mapping to accept a string as identifier but not the other kind of + * identifier (digits or uuid) + */ + public static final String REGEX_REQUESTMAPPING_IDENTIFIER_AS_STRING_VERSION_STRONG = "/{id:^(?!^\\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+\\-]+$+}"; + + /** + * Regular expression in the request mapping to accept number as identifier + */ + public static final String REGEX_REQUESTMAPPING_IDENTIFIER_AS_DIGIT = "/{id:\\d+}"; + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java index ef9c6d8bc8..81b5a2556e 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java @@ -9,9 +9,11 @@ package org.dspace.app.rest.utils; import static java.lang.Integer.parseInt; import static java.util.stream.Collectors.toList; - import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; @@ -19,25 +21,44 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Scanner; - +import java.util.Set; +import java.util.TreeSet; +import java.util.UUID; +import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; +import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.exception.PaginationException; import org.dspace.app.rest.exception.RepositoryNotFoundException; import org.dspace.app.rest.model.AuthorityRest; +import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.app.rest.model.CommunityRest; import org.dspace.app.rest.model.LinkRest; import org.dspace.app.rest.model.LinksRest; +import org.dspace.app.rest.model.ProcessRest; import org.dspace.app.rest.model.ResourcePolicyRest; import org.dspace.app.rest.model.RestAddressableModel; +import org.dspace.app.rest.model.RestModel; import org.dspace.app.rest.model.hateoas.DSpaceResource; +import org.dspace.app.rest.model.hateoas.EmbeddedPage; +import org.dspace.app.rest.model.hateoas.HALResource; +import org.dspace.app.rest.projection.DefaultProjection; +import org.dspace.app.rest.projection.ListProjection; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.repository.DSpaceRestRepository; import org.dspace.app.rest.repository.LinkRestRepository; import org.dspace.content.BitstreamFormat; @@ -46,12 +67,15 @@ import org.dspace.content.service.BitstreamFormatService; import org.dspace.content.service.DSpaceObjectService; import org.dspace.core.ConfigurationManager; import org.dspace.core.Context; +import org.dspace.services.RequestService; import org.dspace.util.UUIDUtils; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; +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.data.domain.Pageable; import org.springframework.hateoas.Link; import org.springframework.stereotype.Component; @@ -67,15 +91,26 @@ public class Utils { private static final Logger log = Logger.getLogger(Utils.class); + private static final int EMBEDDED_PAGE_SIZE = 20; + @Autowired ApplicationContext applicationContext; + @Autowired + RequestService requestService; + @Autowired(required = true) private List> dSpaceObjectServices; @Autowired private BitstreamFormatService bitstreamFormatService; + @Autowired + private ConverterService converter; + + /** Cache to support fast lookups of LinkRest method annotation information. */ + private Map> linkAnnotationForMethod = new HashMap<>(); + public Page getPage(List fullContents, Pageable pageable) { int total = fullContents.size(); List pageContent = null; @@ -135,6 +170,9 @@ public class Utils { if (modelPlural.equals("resourcePolicies")) { return ResourcePolicyRest.NAME; } + if (StringUtils.equals(modelPlural, "processes")) { + return ProcessRest.NAME; + } return modelPlural.replaceAll("s$", ""); } @@ -158,23 +196,13 @@ public class Utils { /** * @param rel - * @param domainClass - * @return the LinkRest annotation corresponding to the specified rel in the - * domainClass. Null if not found + * @param restClass + * @return the LinkRest annotation corresponding to the specified rel in the rest class, or null if not found. */ - public LinkRest getLinkRest(String rel, Class domainClass) { - LinkRest linkRest = null; - LinksRest linksAnnotation = domainClass.getDeclaredAnnotation(LinksRest.class); - if (linksAnnotation != null) { - LinkRest[] links = linksAnnotation.links(); - for (LinkRest l : links) { - if (StringUtils.equals(rel, l.name())) { - linkRest = l; - break; - } - } - } - return linkRest; + public LinkRest getClassLevelLinkRest(String rel, Class restClass) { + Optional optionalLinkRest = getLinkRests(restClass).stream().filter((linkRest) -> + rel.equals(linkRest.name())).findFirst(); + return optionalLinkRest.isPresent() ? optionalLinkRest.get() : null; } /** @@ -183,7 +211,6 @@ public class Utils { * * @param schema * @param element - * @param object * @return */ public String getMetadataKey(String schema, String element, String qualifier) { @@ -225,7 +252,7 @@ public class Utils { /** * Return the filename part from a multipartFile upload that could eventually contains the fullpath on the client - * filesystem + * * @param multipartFile * the file uploaded @@ -285,7 +312,6 @@ public class Utils { * 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) { @@ -355,4 +381,281 @@ public class Utils { } return list; } + + public T toResource(RestModel restObject) { + return converter.toResource(restObject); + } + + /** + * Gets the alphanumerically sorted union of multiple string arrays. + * + * @param arrays the string arrays. + * @return the sorted union of them, with no duplicate values. + */ + public String[] getSortedUnion(String[]... arrays) { + Set set = new TreeSet<>(); + for (String[] array : arrays) { + for (String string : array) { + set.add(string); + } + } + return set.toArray(arrays[0]); + } + + /** + * Gets the method with the given name in the given class. + * + * @param clazz the class. + * @param name the method name. + * @return the first method found with the given name. + * @throws IllegalArgumentException if no such method is found. + */ + public Method requireMethod(Class clazz, String name) { + for (Method method : clazz.getMethods()) { + if (method.getName().equals(name)) { + return method; + } + } + throw new IllegalArgumentException("No such method in " + clazz + ": " + name); + } + + /** + * Gets the projection requested by the current servlet request, or a default projection if none is specified. + * + * @param defaultToList whether to return {@link ListProjection} by default. If false, the no-op + * {@link DefaultProjection} will be returned by default. + * @return the requested or default projection, never {@code null}. + * @throws IllegalArgumentException if the request specifies an unknown projection name. + */ + public Projection obtainProjection(boolean defaultToList) { + String projectionName = requestService.getCurrentRequest().getServletRequest().getParameter("projection"); + if (projectionName == null && defaultToList) { + projectionName = ListProjection.NAME; + } + return converter.getProjection(projectionName); + } + + /** + * Gets the projection requested by the current servlet request, or {@link DefaultProjection} if none + * is specified. + * + * @return the requested or default projection, never {@code null}. + * @throws IllegalArgumentException if the request specifies an unknown projection name. + */ + public Projection obtainProjection() { + return obtainProjection(false); + } + + + + /** + * Adds embeds or links for all class-level LinkRel annotations for which embeds or links are allowed. + * + * @param halResource the resource. + */ + public void embedOrLinkClassLevelRels(HALResource halResource) { + Projection projection = halResource.getContent().getProjection(); + getLinkRests(halResource.getContent().getClass()).stream().forEach((linkRest) -> { + Link link = linkToSubResource(halResource.getContent(), linkRest.name()); + if (!linkRest.embedOptional() || projection.allowOptionalEmbed(halResource, linkRest)) { + embedRelFromRepository(halResource, linkRest.name(), link, linkRest); + halResource.add(link); // unconditionally link if embedding was allowed + } else if (!linkRest.linkOptional() || projection.allowOptionalLink(halResource, linkRest)) { + halResource.add(link); + } + }); + } + + private List getLinkRests(Class restClass) { + List list = new ArrayList<>(); + LinksRest linksAnnotation = restClass.getDeclaredAnnotation(LinksRest.class); + if (linksAnnotation != null) { + list.addAll(Arrays.asList(linksAnnotation.links())); + } + return list; + } + + /** + * Embeds a rel whose value comes from a {@link LinkRestRepository}. + *

+ * The embed will be skipped if 1) the link repository reports that it is not embeddable or 2) the returned + * value is null and the LinkRest annotation has embedOptional = true. + *

+ * Implementation note: The caller is responsible for ensuring that the projection allows the embed + * before calling this method. + *

+ * + * @param resource the resource. + * @param rel the name of the rel. + * @param link the link. + * @param linkRest the LinkRest annotation (must have method defined). + * @throws RepositoryNotFoundException if the link repository could not be found. + * @throws IllegalArgumentException if the method specified by the LinkRest could not be found in the + * link repository. + * @throws RuntimeException if any other problem occurs when trying to invoke the method. + */ + private void embedRelFromRepository(HALResource resource, + String rel, Link link, LinkRest linkRest) { + Projection projection = resource.getContent().getProjection(); + LinkRestRepository linkRepository = getLinkResourceRepository(resource.getContent().getCategory(), + resource.getContent().getType(), rel); + if (linkRepository.isEmbeddableRelation(resource.getContent(), rel)) { + Method method = requireMethod(linkRepository.getClass(), linkRest.method()); + Object contentId = getContentIdForLinkMethod(resource.getContent(), method); + try { + Object linkedObject = method.invoke(linkRepository, null, contentId, null, projection); + if (linkedObject != null || !linkRest.embedOptional()) { + resource.embedResource(rel, wrapForEmbedding(linkedObject, link)); + } + } catch (InvocationTargetException e) { + if (e.getTargetException() instanceof RuntimeException) { + throw (RuntimeException) e.getTargetException(); + } else { + throw new RuntimeException(e); + } + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + } + + /** + * Adds embeds for all properties annotated with {@code @LinkRel} or whose return types are + * {@link RestAddressableModel} subclasses. + */ + public void embedMethodLevelRels(HALResource resource) { + try { + for (PropertyDescriptor pd : Introspector.getBeanInfo( + resource.getContent().getClass()).getPropertyDescriptors()) { + Method readMethod = pd.getReadMethod(); + String propertyName = pd.getName(); + if (readMethod != null && !"class".equals(propertyName)) { + embedMethodLevelRel(resource, readMethod, propertyName); + } + } + } catch (IntrospectionException e) { + throw new RuntimeException(e); + } + } + + /** + * Gets the LinkRest annotation for the given method, if any. + * + * @param readMethod the method. + * @return the annotation, or {@code null} if not found. + */ + public @Nullable LinkRest findLinkAnnotation(Method readMethod) { + Optional optional = linkAnnotationForMethod.get(readMethod); + if (optional == null) { + LinkRest linkRest = AnnotationUtils.findAnnotation(readMethod, LinkRest.class); + optional = linkRest != null ? Optional.of(linkRest) : Optional.empty(); + linkAnnotationForMethod.put(readMethod, optional); + } + return optional.isPresent() ? optional.get() : null; + } + + /** + * Adds an embed for the given property read method. If the @LinkRel annotation is present and + * specifies a method name, the value will come from invoking that method in the appropriate link + * rest repository. Otherwise, the value will come from invoking the method directly on the wrapped + * rest object. + * + * @param readMethod the property read method. + * @param propertyName the property name, which will be used as the rel/embed name unless the @LinkRel + * annotation is present and specifies a different name. + */ + private void embedMethodLevelRel(HALResource resource, + Method readMethod, + String propertyName) { + String rel = propertyName; + LinkRest linkRest = findLinkAnnotation(readMethod); + try { + if (linkRest != null) { + if (linkRest.embedOptional() + && !resource.getContent().getProjection().allowOptionalEmbed(resource, linkRest)) { + return; // projection disallows this optional method-level embed + } + if (StringUtils.isNotBlank(linkRest.name())) { + rel = linkRest.name(); + } + Link link = linkToSubResource(resource.getContent(), rel); + if (StringUtils.isBlank(linkRest.method())) { + Object linkedObject = readMethod.invoke(resource.getContent()); + if (linkedObject != null || !linkRest.embedOptional()) { + resource.embedResource(rel, wrapForEmbedding(linkedObject, link)); + } + } else { + embedRelFromRepository(resource, rel, link, linkRest); + } + } else if (RestAddressableModel.class.isAssignableFrom(readMethod.getReturnType())) { + RestAddressableModel linkedObject = (RestAddressableModel) readMethod.invoke(resource.getContent()); + resource.embedResource(rel, linkedObject == null ? null : + wrapForEmbedding(linkedObject, linkToSubResource(resource.getContent(), rel))); + } + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + /** + * Wraps the given linked object (retrieved from a link repository or link method on the rest item) + * in an object that is appropriate for embedding, if needed. + * + * @param linkedObject the linked object. + * @param link the link, which is used if the linked object is a list or page, to determine the self link + * and embed property name to use for the subresource. + * @return the wrapped object. + */ + private Object wrapForEmbedding(Object linkedObject, Link link) { + if (linkedObject instanceof RestAddressableModel) { + return converter.toResource((RestAddressableModel) linkedObject); + } else if (linkedObject instanceof Page) { + // The first page has already been constructed by a link repository and we only need to wrap it + Page page = (Page) linkedObject; + return new EmbeddedPage(link.getHref(), page.map(converter::toResource), null, link.getRel()); + } else if (linkedObject instanceof List) { + // The full list has been retrieved and we need to provide the first page for embedding + List list = (List) linkedObject; + if (list.size() > 0) { + PageImpl page = new PageImpl( + list.subList(0, list.size() > EMBEDDED_PAGE_SIZE ? EMBEDDED_PAGE_SIZE : list.size()), + new PageRequest(0, EMBEDDED_PAGE_SIZE), list.size()); + return new EmbeddedPage(link.getHref(), + page.map((restObject) -> converter.toResource(restObject)), + list, link.getRel()); + } else { + PageImpl page = new PageImpl(list); + return new EmbeddedPage(link.getHref(), page, list, link.getRel()); + } + } else { + return linkedObject; + } + } + + /** + * Gets an object representing the id of the wrapped object, whose runtime time matches the second + * (id) argument of the given link method. This is necessary because it is possible for the rest + * object's id to be a string while the domain object's id may be a uuid or numeric type. + * + * @param linkMethod the link method. + * @return the id, which may be a UUID, Integer, or Long. + */ + private Object getContentIdForLinkMethod(RestAddressableModel restObject, Method linkMethod) { + Object contentId = ((BaseObjectRest) restObject).getId(); + Class requiredIdType = linkMethod.getParameterTypes()[1]; + if (!requiredIdType.isAssignableFrom(contentId.getClass())) { + if (requiredIdType.equals(UUID.class)) { + contentId = UUID.fromString(contentId.toString()); + } else if (requiredIdType.equals(Integer.class)) { + contentId = Integer.parseInt(contentId.toString()); + } else if (requiredIdType.equals(Long.class)) { + contentId = Long.parseLong(contentId.toString()); + } else { + throw new IllegalArgumentException("Cannot cast " + restObject.getClass() + + " id type " + contentId.getClass() + " to id type required by " + + linkMethod.getDeclaringClass() + "#" + linkMethod.getName() + ": " + requiredIdType); + } + } + return contentId; + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamControllerIT.java new file mode 100644 index 0000000000..98e99d14e2 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamControllerIT.java @@ -0,0 +1,1292 @@ +/** + * 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.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE; +import static org.springframework.http.MediaType.parseMediaType; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.InputStream; +import java.util.List; + +import org.apache.commons.codec.CharEncoding; +import org.apache.commons.io.IOUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.builder.BitstreamBuilder; +import org.dspace.app.rest.builder.BundleBuilder; +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.ResourcePolicyBuilder; +import org.dspace.app.rest.matcher.BundleMatcher; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.BundleService; +import org.dspace.core.Constants; +import org.dspace.eperson.EPerson; +import org.hamcrest.Matchers; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Integration test to test the /api/core/bitstreams/[id]/content endpoint + * + * @author Tom Desair (tom dot desair at atmire dot com) + * @author Frederic Van Reet (frederic dot vanreet at atmire dot com) + */ +public class BitstreamControllerIT extends AbstractControllerIntegrationTest { + + @Autowired + BundleService bundleService; + + @Autowired + BitstreamService bitstreamService; + + private static final Logger log = LogManager.getLogger(BitstreamControllerIT.class); + + @Test + public void getBundle() throws Exception { + //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. + 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(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + + Bundle bundle1 = BundleBuilder.createBundle(context, publicItem1) + .withName("ORIGINAL") + .build(); + + //Add a bitstream to an item + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType("text/plain") + .build(); + } + + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/bundle")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.is( + BundleMatcher.matchBundle(bundle1.getName(), + bundle1.getID(), + bundle1.getHandle(), + bundle1.getType(), + bundle1.getBitstreams()) + + ))); + + } + + @Test + public void getFirstBundleWhenMultipleBundles() throws Exception { + + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + 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(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + + Bundle bundle1 = BundleBuilder.createBundle(context, publicItem1) + .withName("TEST FIRST BUNDLE") + .build(); + + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder. + createBitstream(context, bundle1, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType("text/plain") + .build(); + } + + Bundle bundle2 = BundleBuilder.createBundle(context, publicItem1) + .withName("TEST SECOND BUNDLE") + .withBitstream(bitstream) + .build(); + + + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/bundle")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.is( + BundleMatcher.matchBundle(bundle1.getName(), + bundle1.getID(), + bundle1.getHandle(), + bundle1.getType(), + bundle1.getBitstreams()) + + ))).andExpect(jsonPath("$", Matchers.not( + BundleMatcher.matchBundle(bundle2.getName(), + bundle2.getID(), + bundle2.getHandle(), + bundle2.getType(), + bundle2.getBitstreams()) + ))); + + + } + + @Test + public void getBundleWhenNoBundle() throws Exception { + + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + 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(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + + Bundle bundle1 = BundleBuilder.createBundle(context, publicItem1) + .withName("TEST FIRST BUNDLE") + .build(); + + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder. + createBitstream(context, bundle1, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType("text/plain") + .build(); + } + + bundleService.removeBitstream(context, bundle1, bitstream); + bundleService.update(context, bundle1); + bitstreamService.update(context, bitstream); + + context.restoreAuthSystemState(); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/bundle")) + .andExpect(status().isNoContent()); + + } + + @Test + public void putOnBitstreamInOneBundle() throws Exception { + + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + 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(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + Item targetItem = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + + Bundle bundle1 = BundleBuilder.createBundle(context, publicItem1) + .withName("TEST FIRST BUNDLE") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder. + createBitstream(context, bundle1, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType("text/plain") + .build(); + } + + Bundle targetBundle = BundleBuilder.createBundle(context, targetItem) + .withName("TARGET BUNDLE") + .build(); + + + EPerson putBundlePerson = EPersonBuilder.createEPerson(context).withEmail("bundle@pput.org") + .withPassword("test") + .withNameInMetadata("Bundle", "Put").build(); + + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.REMOVE) + .withDspaceObject(bundle1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(bundle1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(targetBundle).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.ADD) + .withDspaceObject(targetBundle).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(bitstream).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(publicItem1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(targetItem).build(); + + + context.restoreAuthSystemState(); + String token = getAuthToken(putBundlePerson.getEmail(), "test"); + + getClient(token) + .perform(put("/api/core/bitstreams/" + bitstream.getID() + "/bundle") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/bundles/" + targetBundle.getID() + )) + .andExpect(status().isOk()) + ; + + targetBundle = bundleService.find(context, targetBundle.getID()); + String name = targetBundle.getName(); + String handle = targetBundle.getHandle(); + List bitstreams = targetBundle.getBitstreams(); + getClient(token).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/bundle")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.is( + BundleMatcher.matchBundle(name, + targetBundle.getID(), + handle, + targetBundle.getType(), + bitstreams) + ))); + + + } + + @Test + public void putOnBitstreamInMultipleBundles() throws Exception { + + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + 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(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + Item targetItem = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + + Bundle bundle1 = BundleBuilder.createBundle(context, publicItem1) + .withName("TEST FIRST BUNDLE") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder. + createBitstream(context, bundle1, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType("text/plain") + .build(); + } + + Bundle bundle2 = BundleBuilder.createBundle(context, publicItem1) + .withName("TEST SECOND BUNDLE") + .withBitstream(bitstream) + .build(); + + Bundle targetBundle = BundleBuilder.createBundle(context, targetItem) + .withName("TARGET BUNDLE") + .build(); + + + EPerson putBundlePerson = EPersonBuilder.createEPerson(context).withEmail("bundle@pput.org") + .withPassword("test") + .withNameInMetadata("Bundle", "Put").build(); + + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.REMOVE) + .withDspaceObject(bundle1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(bundle1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.REMOVE) + .withDspaceObject(bundle2).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(bundle2).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(targetBundle).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.ADD) + .withDspaceObject(targetBundle).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(bitstream).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(publicItem1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(targetItem).build(); + + + context.restoreAuthSystemState(); + String token = getAuthToken(putBundlePerson.getEmail(), "test"); + + getClient(token) + .perform(put("/api/core/bitstreams/" + bitstream.getID() + "/bundle") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/bundles/" + targetBundle.getID() + )) + .andExpect(status().isOk()) + ; + + targetBundle = bundleService.find(context, targetBundle.getID()); + String name = targetBundle.getName(); + String handle = targetBundle.getHandle(); + List bitstreams = targetBundle.getBitstreams(); + + getClient(token).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/bundle")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.is( + BundleMatcher.matchBundle(name, + targetBundle.getID(), + handle, + targetBundle.getType(), + bitstreams) + ))); + + + } + + @Test + public void putOnBitstreamInNoBundle() throws Exception { + + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + 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(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + Item targetItem = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + + Bundle bundle1 = BundleBuilder.createBundle(context, publicItem1) + .withName("TEST FIRST BUNDLE") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder. + createBitstream(context, bundle1, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType("text/plain") + .build(); + } + + bundleService.removeBitstream(context, bundle1, bitstream); + bundleService.update(context, bundle1); + bitstreamService.update(context, bitstream); + + Bundle targetBundle = BundleBuilder.createBundle(context, targetItem) + .withName("TARGET BUNDLE") + .build(); + + + EPerson putBundlePerson = EPersonBuilder.createEPerson(context).withEmail("bundle@pput.org") + .withPassword("test") + .withNameInMetadata("Bundle", "Put").build(); + + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.REMOVE) + .withDspaceObject(bundle1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(bundle1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(targetBundle).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.ADD) + .withDspaceObject(targetBundle).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(bitstream).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(publicItem1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(targetItem).build(); + + + context.restoreAuthSystemState(); + String token = getAuthToken(putBundlePerson.getEmail(), "test"); + + getClient(token) + .perform(put("/api/core/bitstreams/" + bitstream.getID() + "/bundle") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/bundles/" + targetBundle.getID() + )).andExpect(status().isOk()); + + targetBundle = bundleService.find(context, targetBundle.getID()); + String name = targetBundle.getName(); + String handle = targetBundle.getHandle(); + List bitstreams = targetBundle.getBitstreams(); + + + getClient(token).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/bundle")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.is( + BundleMatcher.matchBundle(name, + targetBundle.getID(), + handle, + targetBundle.getType(), + bitstreams) + ))); + + + } + + @Test + public void putOnBitstreamInOneBundleWithNoRemoveRights() throws Exception { + + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + 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(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + Item targetItem = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + + Bundle bundle1 = BundleBuilder.createBundle(context, publicItem1) + .withName("TEST FIRST BUNDLE") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder. + createBitstream(context, bundle1, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType("text/plain") + .build(); + } + + Bundle targetBundle = BundleBuilder.createBundle(context, targetItem) + .withName("TARGET BUNDLE") + .build(); + + + EPerson putBundlePerson = EPersonBuilder.createEPerson(context).withEmail("bundle@pput.org") + .withPassword("test") + .withNameInMetadata("Bundle", "Put").build(); + + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(bundle1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(targetBundle).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.ADD) + .withDspaceObject(targetBundle).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(bitstream).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(publicItem1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(targetItem).build(); + + + context.restoreAuthSystemState(); + String token = getAuthToken(putBundlePerson.getEmail(), "test"); + + getClient(token) + .perform(put("/api/core/bitstreams/" + bitstream.getID() + "/bundle") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/bundles/" + targetBundle.getID() + )) + .andExpect(status().isForbidden()); + + getClient(token).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/bundle")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.is( + BundleMatcher.matchBundle(bundle1.getName(), + bundle1.getID(), + bundle1.getHandle(), + bundle1.getType(), + bundle1.getBitstreams()) + + ))); + + + } + + @Test + public void putOnBitstreamInOneBundleWithNoWriteOnCurrentBundleRights() throws Exception { + + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + 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(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + Item targetItem = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + + Bundle bundle1 = BundleBuilder.createBundle(context, publicItem1) + .withName("TEST FIRST BUNDLE") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder. + createBitstream(context, bundle1, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType("text/plain") + .build(); + } + + Bundle targetBundle = BundleBuilder.createBundle(context, targetItem) + .withName("TARGET BUNDLE") + .build(); + + + EPerson putBundlePerson = EPersonBuilder.createEPerson(context).withEmail("bundle@pput.org") + .withPassword("test") + .withNameInMetadata("Bundle", "Put").build(); + + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.REMOVE) + .withDspaceObject(bundle1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(targetBundle).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.ADD) + .withDspaceObject(targetBundle).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(bitstream).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(publicItem1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(targetItem).build(); + + + context.restoreAuthSystemState(); + String token = getAuthToken(putBundlePerson.getEmail(), "test"); + + getClient(token) + .perform(put("/api/core/bitstreams/" + bitstream.getID() + "/bundle") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/bundles/" + targetBundle.getID() + )) + .andExpect(status().isForbidden()); + + + getClient(token).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/bundle")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.is( + BundleMatcher.matchBundle(bundle1.getName(), + bundle1.getID(), + bundle1.getHandle(), + bundle1.getType(), + bundle1.getBitstreams()) + + ))); + + + } + + @Test + public void putOnBitstreamInOneBundleWithNoWriteRightsOnTargetBundle() throws Exception { + + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + 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(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + Item targetItem = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + + Bundle bundle1 = BundleBuilder.createBundle(context, publicItem1) + .withName("TEST FIRST BUNDLE") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder. + createBitstream(context, bundle1, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType("text/plain") + .build(); + } + + Bundle targetBundle = BundleBuilder.createBundle(context, targetItem) + .withName("TARGET BUNDLE") + .build(); + + + EPerson putBundlePerson = EPersonBuilder.createEPerson(context).withEmail("bundle@pput.org") + .withPassword("test") + .withNameInMetadata("Bundle", "Put").build(); + + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.REMOVE) + .withDspaceObject(bundle1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(bundle1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.ADD) + .withDspaceObject(targetBundle).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(bitstream).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(publicItem1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(targetItem).build(); + + + context.restoreAuthSystemState(); + String token = getAuthToken(putBundlePerson.getEmail(), "test"); + + getClient(token) + .perform(put("/api/core/bitstreams/" + bitstream.getID() + "/bundle") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/bundles/" + targetBundle.getID() + )) + .andExpect(status().isForbidden()); + + getClient(token).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/bundle")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.is( + BundleMatcher.matchBundle(bundle1.getName(), + bundle1.getID(), + bundle1.getHandle(), + bundle1.getType(), + bundle1.getBitstreams()) + + ))); + + + } + + @Test + public void putOnBitstreamInOneBundleWithNoAddRightsOnTargetBundle() throws Exception { + + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + 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(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + Item targetItem = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + + Bundle bundle1 = BundleBuilder.createBundle(context, publicItem1) + .withName("TEST FIRST BUNDLE") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder. + createBitstream(context, bundle1, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType("text/plain") + .build(); + } + + Bundle targetBundle = BundleBuilder.createBundle(context, targetItem) + .withName("TARGET BUNDLE") + .build(); + + + EPerson putBundlePerson = EPersonBuilder.createEPerson(context).withEmail("bundle@pput.org") + .withPassword("test") + .withNameInMetadata("Bundle", "Put").build(); + + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.REMOVE) + .withDspaceObject(bundle1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(bundle1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(targetBundle).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(bitstream).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(publicItem1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(targetItem).build(); + + + context.restoreAuthSystemState(); + String token = getAuthToken(putBundlePerson.getEmail(), "test"); + + getClient(token) + .perform(put("/api/core/bitstreams/" + bitstream.getID() + "/bundle") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/bundles/" + targetBundle.getID() + )) + .andExpect(status().isForbidden()); + + getClient(token).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/bundle")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.is( + BundleMatcher.matchBundle(bundle1.getName(), + bundle1.getID(), + bundle1.getHandle(), + bundle1.getType(), + bundle1.getBitstreams()) + + ))); + + + } + + @Test + public void putOnBitstreamInOneBundleWithNoWriteRightsOnBitstream() throws Exception { + + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + 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(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + Item targetItem = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + + Bundle bundle1 = BundleBuilder.createBundle(context, publicItem1) + .withName("TEST FIRST BUNDLE") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder. + createBitstream(context, bundle1, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType("text/plain") + .build(); + } + + Bundle targetBundle = BundleBuilder.createBundle(context, targetItem) + .withName("TARGET BUNDLE") + .build(); + + + EPerson putBundlePerson = EPersonBuilder.createEPerson(context).withEmail("bundle@pput.org") + .withPassword("test") + .withNameInMetadata("Bundle", "Put").build(); + + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.REMOVE) + .withDspaceObject(bundle1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(bundle1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(targetBundle).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.ADD) + .withDspaceObject(targetBundle).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(publicItem1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(targetItem).build(); + + + context.restoreAuthSystemState(); + String token = getAuthToken(putBundlePerson.getEmail(), "test"); + + getClient(token) + .perform(put("/api/core/bitstreams/" + bitstream.getID() + "/bundle") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/bundles/" + targetBundle.getID() + )) + .andExpect(status().isForbidden()); + + + getClient(token).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/bundle")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.is( + BundleMatcher.matchBundle(bundle1.getName(), + bundle1.getID(), + bundle1.getHandle(), + bundle1.getType(), + bundle1.getBitstreams()) + + ))); + + + } + + @Test + public void putOnBitstreamInOneBundleWithNoWriteRightsOnCurrentItem() throws Exception { + + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + 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(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + Item targetItem = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + + Bundle bundle1 = BundleBuilder.createBundle(context, publicItem1) + .withName("TEST FIRST BUNDLE") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder. + createBitstream(context, bundle1, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType("text/plain") + .build(); + } + + Bundle targetBundle = BundleBuilder.createBundle(context, targetItem) + .withName("TARGET BUNDLE") + .build(); + + + EPerson putBundlePerson = EPersonBuilder.createEPerson(context).withEmail("bundle@pput.org") + .withPassword("test") + .withNameInMetadata("Bundle", "Put").build(); + + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.REMOVE) + .withDspaceObject(bundle1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(bundle1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(targetBundle).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.ADD) + .withDspaceObject(targetBundle).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(bitstream).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(targetItem).build(); + + + context.restoreAuthSystemState(); + String token = getAuthToken(putBundlePerson.getEmail(), "test"); + + getClient(token) + .perform(put("/api/core/bitstreams/" + bitstream.getID() + "/bundle") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/bundles/" + targetBundle.getID() + )) + .andExpect(status().isForbidden()); + + getClient(token).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/bundle")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.is( + BundleMatcher.matchBundle(bundle1.getName(), + bundle1.getID(), + bundle1.getHandle(), + bundle1.getType(), + bundle1.getBitstreams()) + + ))); + + + } + + @Test + public void putOnBitstreamInOneBundleWithNoWriteRightsOnTargetItem() throws Exception { + + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + 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(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + Item targetItem = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + + Bundle bundle1 = BundleBuilder.createBundle(context, publicItem1) + .withName("TEST FIRST BUNDLE") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder. + createBitstream(context, bundle1, is) + .withName("Bitstream") + .withDescription("description") + .withMimeType("text/plain") + .build(); + } + + Bundle targetBundle = BundleBuilder.createBundle(context, targetItem) + .withName("TARGET BUNDLE") + .build(); + + + EPerson putBundlePerson = EPersonBuilder.createEPerson(context).withEmail("bundle@pput.org") + .withPassword("test") + .withNameInMetadata("Bundle", "Put").build(); + + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.REMOVE) + .withDspaceObject(bundle1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(bundle1).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(targetBundle).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.ADD) + .withDspaceObject(targetBundle).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(bitstream).build(); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(putBundlePerson) + .withAction(Constants.WRITE) + .withDspaceObject(publicItem1).build(); + + + context.restoreAuthSystemState(); + String token = getAuthToken(putBundlePerson.getEmail(), "test"); + + getClient(token) + .perform(put("/api/core/bitstreams/" + bitstream.getID() + "/bundle") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content( + "https://localhost:8080/spring-rest/api/core/bundles/" + targetBundle.getID() + )) + .andExpect(status().isForbidden()); + + getClient(token).perform(get("/api/core/bitstreams/" + bitstream.getID() + "/bundle")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.is( + BundleMatcher.matchBundle(bundle1.getName(), + bundle1.getID(), + bundle1.getHandle(), + bundle1.getType(), + bundle1.getBitstreams()) + + ))); + + + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamFormatRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamFormatRestRepositoryIT.java index fa6ecaedfe..c2eda5bb64 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamFormatRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamFormatRestRepositoryIT.java @@ -27,9 +27,10 @@ import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.rest.builder.BitstreamFormatBuilder; import org.dspace.app.rest.builder.EPersonBuilder; -import org.dspace.app.rest.converter.BitstreamFormatConverter; +import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.matcher.BitstreamFormatMatcher; import org.dspace.app.rest.model.BitstreamFormatRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.content.BitstreamFormat; import org.dspace.content.service.BitstreamFormatService; @@ -49,7 +50,7 @@ import org.springframework.test.web.servlet.MvcResult; public class BitstreamFormatRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired - BitstreamFormatConverter converter; + ConverterService converter; @Autowired BitstreamFormatService bitstreamFormatService; @@ -279,7 +280,7 @@ public class BitstreamFormatRestRepositoryIT extends AbstractControllerIntegrati .build(); context.restoreAuthSystemState(); - BitstreamFormatRest bitstreamFormatRest = converter.fromModel(bitstreamFormat); + BitstreamFormatRest bitstreamFormatRest = converter.toRest(bitstreamFormat, Projection.DEFAULT); String token = getAuthToken(admin.getEmail(), password); //Update it bitstreamFormatRest.setShortDescription("Test short UPDATED"); @@ -311,7 +312,7 @@ public class BitstreamFormatRestRepositoryIT extends AbstractControllerIntegrati .build(); context.restoreAuthSystemState(); - BitstreamFormatRest bitstreamFormatRest = converter.fromModel(bitstreamFormat); + BitstreamFormatRest bitstreamFormatRest = converter.toRest(bitstreamFormat, Projection.DEFAULT); String token = getAuthToken(admin.getEmail(), password); //Update it bitstreamFormatRest.setShortDescription("Test short UPDATED"); @@ -349,7 +350,7 @@ public class BitstreamFormatRestRepositoryIT extends AbstractControllerIntegrati int nonExistentBitstreamFormatID = 404404404; - BitstreamFormatRest bitstreamFormatRest = converter.fromModel(bitstreamFormat); + BitstreamFormatRest bitstreamFormatRest = converter.toRest(bitstreamFormat, Projection.DEFAULT); String token = getAuthToken(admin.getEmail(), password); //Update it with non existent ID in URL and in JSON bitstreamFormatRest.setShortDescription("Test short UPDATED"); @@ -386,7 +387,7 @@ public class BitstreamFormatRestRepositoryIT extends AbstractControllerIntegrati int nonExistentBitstreamFormatID = 404404404; - BitstreamFormatRest bitstreamFormatRest = converter.fromModel(bitstreamFormat); + BitstreamFormatRest bitstreamFormatRest = converter.toRest(bitstreamFormat, Projection.DEFAULT); String token = getAuthToken(admin.getEmail(), password); //Update it with non existent ID in URL bitstreamFormatRest.setShortDescription("Test short UPDATED"); @@ -422,7 +423,7 @@ public class BitstreamFormatRestRepositoryIT extends AbstractControllerIntegrati int nonExistentBitstreamFormatID = 404404404; - BitstreamFormatRest bitstreamFormatRest = converter.fromModel(bitstreamFormat); + BitstreamFormatRest bitstreamFormatRest = converter.toRest(bitstreamFormat, Projection.DEFAULT); String token = getAuthToken(admin.getEmail(), password); //Update it with non existent ID in JSON, but valid in URL bitstreamFormatRest.setShortDescription("Test short UPDATED"); @@ -460,7 +461,7 @@ public class BitstreamFormatRestRepositoryIT extends AbstractControllerIntegrati .build(); context.restoreAuthSystemState(); - BitstreamFormatRest bitstreamFormatRest = converter.fromModel(bitstreamFormat1); + BitstreamFormatRest bitstreamFormatRest = converter.toRest(bitstreamFormat1, Projection.DEFAULT); String token = getAuthToken(admin.getEmail(), password); //Update but id in body is not same id as in URL bitstreamFormatRest.setShortDescription("Test short UPDATED"); @@ -493,7 +494,7 @@ public class BitstreamFormatRestRepositoryIT extends AbstractControllerIntegrati .build(); context.restoreAuthSystemState(); - BitstreamFormatRest bitstreamFormatRest = converter.fromModel(bitstreamFormat); + BitstreamFormatRest bitstreamFormatRest = converter.toRest(bitstreamFormat, Projection.DEFAULT); //Try to update bitstreamFormat without auth token bitstreamFormatRest.setShortDescription("Test short UPDATED"); @@ -529,7 +530,7 @@ public class BitstreamFormatRestRepositoryIT extends AbstractControllerIntegrati .build(); context.restoreAuthSystemState(); - BitstreamFormatRest bitstreamFormatRest = converter.fromModel(bitstreamFormat); + BitstreamFormatRest bitstreamFormatRest = converter.toRest(bitstreamFormat, Projection.DEFAULT); String token = getAuthToken(user.getEmail(), password); //Try to update bitstreamFormat without non-admin auth token @@ -654,4 +655,4 @@ public class BitstreamFormatRestRepositoryIT extends AbstractControllerIntegrati bitstreamFormatRest.setExtensions(Arrays.asList("txt", "asc")); return bitstreamFormatRest; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java new file mode 100644 index 0000000000..ca93948f0a --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java @@ -0,0 +1,426 @@ +/** + * 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 com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import javax.ws.rs.core.MediaType; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.codec.CharEncoding; +import org.apache.commons.io.IOUtils; +import org.dspace.app.rest.builder.BitstreamBuilder; +import org.dspace.app.rest.builder.BundleBuilder; +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.ResourcePolicyBuilder; +import org.dspace.app.rest.matcher.BitstreamMatcher; +import org.dspace.app.rest.matcher.BundleMatcher; +import org.dspace.app.rest.matcher.MetadataMatcher; +import org.dspace.app.rest.model.BundleRest; +import org.dspace.app.rest.model.MetadataRest; +import org.dspace.app.rest.model.MetadataValueRest; +import org.dspace.app.rest.model.patch.MoveOperation; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.core.Constants; +import org.dspace.eperson.EPerson; +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; +import org.springframework.test.web.servlet.MvcResult; + +public class BundleRestRepositoryIT extends AbstractControllerIntegrationTest { + + private Collection collection; + private Item item; + private Bundle bundle1; + private Bundle bundle2; + private Bitstream bitstream1; + private Bitstream bitstream2; + + @Before + public void setUp() throws Exception { + super.setUp(); + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + collection = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + + item = ItemBuilder.createItem(context, collection) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + + context.restoreAuthSystemState(); + + } + + @Test + public void GetSingleBundle() throws Exception { + context.turnOffAuthorisationSystem(); + + String bitstreamContent = "Dummy content"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder.createBitstream(context, item, is) + .withName("Bitstream") + .withMimeType("text/plain") + .build(); + } + + bundle1 = BundleBuilder.createBundle(context, item) + .withName("testname") + .withBitstream(bitstream1) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/bundles/" + bundle1.getID())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", BundleMatcher.matchBundle(bundle1.getName(), + bundle1.getID(), + bundle1.getHandle(), + bundle1.getType(), + bundle1.getBitstreams()) + )) + .andExpect(jsonPath("$._embedded.bitstreams._embedded.bitstreams", containsInAnyOrder( + BitstreamMatcher.matchBitstreamEntry(bitstream1.getID(), bitstream1.getSizeBytes()))) + ) + ; + } + + + @Test + public void getItemBundles() throws Exception { + + context.turnOffAuthorisationSystem(); + + String bitstreamContent = "Dummy content"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder.createBitstream(context, item, is) + .withName("Bitstream") + .withMimeType("text/plain") + .build(); + } + + bundle1 = BundleBuilder.createBundle(context, item) + .withName("testname") + .withBitstream(bitstream1) + .build(); + bundle2 = BundleBuilder.createBundle(context, item).withName("test2").build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/items/" + item.getID() + "/bundles")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.bundles", Matchers.hasItems( + BundleMatcher.matchBundle(bundle1.getName(), + bundle1.getID(), + bundle1.getHandle(), + bundle1.getType(), + bundle1.getBitstreams()) + , + BundleMatcher.matchBundle(bundle2.getName(), + bundle2.getID(), + bundle2.getHandle(), + bundle2.getType(), + bundle2.getBitstreams()) + + ))) + .andExpect(jsonPath("$._links.self.href", endsWith("/api/core/items/" + item.getID() + "/bundles"))) + ; + } + + @Test + public void createBundleWithoutMetadata() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + BundleRest bundleRest = new BundleRest(); + bundleRest.setName("Create Bundle Without Metadata"); + + String token = getAuthToken(admin.getEmail(), password); + + MvcResult mvcResult = getClient(token).perform(post("/api/core/items/" + item.getID() + "/bundles") + .content(mapper.writeValueAsBytes(bundleRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andReturn(); + + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + UUID bundleUuid = UUID.fromString(String.valueOf(map.get("uuid"))); + + + getClient().perform(get("/api/core/bundles/" + bundleUuid)) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", BundleMatcher.matchBundle( + "Create Bundle Without Metadata", + bundleUuid, null, Constants.BUNDLE, new ArrayList<>()))); + } + + @Test + public void createBundleWithMetadata() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + + BundleRest bundleRest = new BundleRest(); + bundleRest.setName("Create Bundle Without Metadata"); + bundleRest.setMetadata(new MetadataRest() + .put("dc.description", + new MetadataValueRest("A description")) + .put("dc.relation", + new MetadataValueRest("A relation"))); + + + String token = getAuthToken(admin.getEmail(), password); + + MvcResult mvcResult = getClient(token).perform(post("/api/core/items/" + item.getID() + "/bundles") + .content(mapper.writeValueAsBytes(bundleRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andReturn(); + + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + UUID bundleUuid = UUID.fromString(String.valueOf(map.get("uuid"))); + + + getClient().perform(get("/api/core/bundles/" + bundleUuid)) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", BundleMatcher.matchBundle( + "Create Bundle Without Metadata", + bundleUuid, null, Constants.BUNDLE, new ArrayList<>()))) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.metadata", Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", + "A description"), + MetadataMatcher.matchMetadata("dc.relation", + "A relation")))))); + } + + @Test + public void createBundleAsAnonymous() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + + BundleRest bundleRest = new BundleRest(); + bundleRest.setName("Create Bundle Without Metadata"); + + getClient().perform(post("/api/core/items/" + item.getID() + "/bundles") + .content(mapper.writeValueAsBytes(bundleRest)).contentType(contentType)) + .andExpect(status().isUnauthorized()); + + getClient().perform(get("/api/core/items/" + item.getID() + "/bundles")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + } + + @Test + public void createBundleWithInsufficientPermissions() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + + BundleRest bundleRest = new BundleRest(); + bundleRest.setName("Create Bundle Without Metadata"); + + String token = getAuthToken(eperson.getEmail(), password); + + + getClient(token).perform(post("/api/core/items/" + item.getID() + "/bundles") + .content(mapper.writeValueAsBytes(bundleRest)).contentType(contentType)) + .andExpect(status().isForbidden()); + + getClient().perform(get("/api/core/items/" + item.getID() + "/bundles")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + } + + @Test + public void createBundleWithSufficientPermissions() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + + context.turnOffAuthorisationSystem(); + + EPerson createBundleEperson = EPersonBuilder.createEPerson(context).withEmail("createm@bundle.org") + .withPassword("test") + .withNameInMetadata("Create", "Bundle").build(); + + ResourcePolicy rp1 = ResourcePolicyBuilder.createResourcePolicy(context).withUser(createBundleEperson) + .withAction(Constants.ADD) + .withDspaceObject(item).build(); + context.restoreAuthSystemState(); + + + BundleRest bundleRest = new BundleRest(); + bundleRest.setName("Create Bundle Without Metadata"); + + + String token = getAuthToken(createBundleEperson.getEmail(), "test"); + + + MvcResult mvcResult = getClient(token).perform(post("/api/core/items/" + item.getID() + "/bundles") + .content(mapper.writeValueAsBytes(bundleRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andReturn(); + + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + UUID bundleUuid = UUID.fromString(String.valueOf(map.get("uuid"))); + + + getClient().perform(get("/api/core/bundles/" + bundleUuid)) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", BundleMatcher.matchBundle( + "Create Bundle Without Metadata", + bundleUuid, null, Constants.BUNDLE, new ArrayList<>()))); + + } + + @Test + public void createBundleOnNonExistingItem() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + + BundleRest bundleRest = new BundleRest(); + bundleRest.setName("Create Bundle Without Metadata"); + + String token = getAuthToken(eperson.getEmail(), password); + + + getClient(token).perform(post("/api/core/items/" + UUID.randomUUID() + "/bundles") + .content(mapper.writeValueAsBytes(bundleRest)).contentType(contentType)) + .andExpect(status().isNotFound()); + + } + + @Test + public void getBitstreamsForBundle() throws Exception { + context.turnOffAuthorisationSystem(); + + String bitstreamContent = "Dummy content"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder.createBitstream(context, item, is) + .withName("Bitstream") + .withDescription("Description") + .withMimeType("text/plain") + .build(); + bitstream2 = BitstreamBuilder.createBitstream(context, item, is) + .withName("Bitstream2") + .withDescription("Description2") + .withMimeType("text/plain") + .build(); + } + + bundle1 = BundleBuilder.createBundle(context, item) + .withName("testname") + .withBitstream(bitstream1) + .withBitstream(bitstream2) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/bundles/" + bundle1.getID() + "/bitstreams")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.bitstreams", Matchers.hasItems( + BitstreamMatcher.matchBitstreamEntry(bitstream1), + BitstreamMatcher.matchBitstreamEntry(bitstream2) + ))); + } + + @Test + public void patchMoveBitstreams() throws Exception { + context.turnOffAuthorisationSystem(); + + String bitstreamContent = "Dummy content"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream1 = BitstreamBuilder.createBitstream(context, item, is) + .withName("Bitstream") + .withDescription("Description") + .withMimeType("text/plain") + .build(); + bitstream2 = BitstreamBuilder.createBitstream(context, item, is) + .withName("Bitstream2") + .withDescription("Description2") + .withMimeType("text/plain") + .build(); + } + + bundle1 = BundleBuilder.createBundle(context, item) + .withName("testname") + .withBitstream(bitstream1) + .withBitstream(bitstream2) + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/bundles/" + bundle1.getID() + "/bitstreams")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.bitstreams", Matchers.contains( + BitstreamMatcher.matchBitstreamEntry(bitstream1), + BitstreamMatcher.matchBitstreamEntry(bitstream2) + ))); + + List ops = new ArrayList(); + MoveOperation moveOperation = new MoveOperation("/_links/bitstreams/0/href", + "/_links/bitstreams/1/href"); + ops.add(moveOperation); + String patchBody = getPatchContent(ops); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(patch("/api/core/bundles/" + bundle1.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + getClient().perform(get("/api/core/bundles/" + bundle1.getID() + "/bitstreams")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.bitstreams", Matchers.contains( + BitstreamMatcher.matchBitstreamEntry(bitstream2), + BitstreamMatcher.matchBitstreamEntry(bitstream1) + ))); + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemUploadControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleUploadBitstreamControllerIT.java similarity index 66% rename from dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemUploadControllerIT.java rename to dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleUploadBitstreamControllerIT.java index 5207caa928..21213ec84a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemUploadControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleUploadBitstreamControllerIT.java @@ -18,6 +18,7 @@ import java.util.Map; import java.util.UUID; import com.fasterxml.jackson.databind.ObjectMapper; +import org.dspace.app.rest.builder.BundleBuilder; import org.dspace.app.rest.builder.CollectionBuilder; import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.builder.ItemBuilder; @@ -28,6 +29,7 @@ import org.dspace.app.rest.model.MetadataRest; import org.dspace.app.rest.model.MetadataValueRest; import org.dspace.app.rest.test.AbstractEntityIntegrationTest; import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; @@ -41,7 +43,7 @@ import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -public class ItemUploadControllerIT extends AbstractEntityIntegrationTest { +public class BundleUploadBitstreamControllerIT extends AbstractEntityIntegrationTest { @Autowired private AuthorizeService authorizeService; @@ -58,8 +60,6 @@ public class ItemUploadControllerIT extends AbstractEntityIntegrationTest { .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 item = ItemBuilder.createItem(context, col1) .withTitle("Author1") @@ -67,6 +67,10 @@ public class ItemUploadControllerIT extends AbstractEntityIntegrationTest { .withAuthor("Smith, Donald") .build(); + Bundle bundle = BundleBuilder.createBundle(context, item) + .withName("TESTINGBUNDLE") + .build(); + String token = getAuthToken(admin.getEmail(), password); String input = "Hello, World!"; @@ -74,7 +78,6 @@ public class ItemUploadControllerIT extends AbstractEntityIntegrationTest { input.getBytes()); BitstreamRest bitstreamRest = new BitstreamRest(); - bitstreamRest.setBundleName("TESTINGBUNDLE"); bitstreamRest.setName("testing"); MetadataRest metadataRest = new MetadataRest(); @@ -100,33 +103,36 @@ public class ItemUploadControllerIT extends AbstractEntityIntegrationTest { context.restoreAuthSystemState(); MvcResult mvcResult = getClient(token).perform( - MockMvcRequestBuilders.fileUpload("/api/core/items/" + item.getID() + "/bitstreams") - .file(file) - .param("properties", mapper - .writeValueAsString(bitstreamRest))) - .andExpect(status().isOk()) + MockMvcRequestBuilders.fileUpload("/api/core/bundles/" + bundle.getID() + "/bitstreams") + .file(file) + .param("properties", mapper + .writeValueAsString(bitstreamRest))) + .andExpect(status().isCreated()) .andExpect(jsonPath("$.name", is("testing"))) .andExpect(jsonPath("$.bundleName", is("TESTINGBUNDLE"))) .andExpect(jsonPath("$", Matchers.allOf( - hasJsonPath("$.metadata", Matchers.allOf( - MetadataMatcher.matchMetadata("dc.description", - "description"), - MetadataMatcher.matchMetadata("dc.description.tableofcontents", - "News"), - MetadataMatcher.matchMetadata("dc.rights", - "Custom Copyright Text"), - MetadataMatcher.matchMetadata("dc.title", - "testing") - ))))).andReturn(); + hasJsonPath("$.metadata", Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", + "description"), + MetadataMatcher + .matchMetadata("dc.description.tableofcontents", + "News"), + MetadataMatcher.matchMetadata("dc.rights", + "Custom Copyright Text"), + MetadataMatcher.matchMetadata("dc.title", + "testing") + ))))).andReturn(); String content = mvcResult.getResponse().getContentAsString(); Map map = mapper.readValue(content, Map.class); String bitstreamId = String.valueOf(map.get("id")); - getClient(token).perform(get("/api/core/items/" + item.getID() + "/bitstreams")) + + getClient(token).perform(get("/api/core/bundles/" + bundle.getID() + "/bitstreams")) .andExpect(status().isOk()) .andExpect(jsonPath("_embedded.bitstreams", Matchers.hasItem( - BitstreamMatcher.matchBitstreamEntry(UUID.fromString(bitstreamId), file.getSize())))); + BitstreamMatcher.matchBitstreamEntry(UUID.fromString(bitstreamId), file.getSize(), + bitstreamRest.getName(), "description")))); } @@ -142,8 +148,6 @@ public class ItemUploadControllerIT extends AbstractEntityIntegrationTest { .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 item = ItemBuilder.createItem(context, col1) .withTitle("Author1") @@ -151,6 +155,10 @@ public class ItemUploadControllerIT extends AbstractEntityIntegrationTest { .withAuthor("Smith, Donald") .build(); + Bundle bundle = BundleBuilder.createBundle(context, item) + .withName("TESTINGBUNDLE") + .build(); + String token = getAuthToken(admin.getEmail(), password); String input = "Hello, World!"; @@ -158,13 +166,13 @@ public class ItemUploadControllerIT extends AbstractEntityIntegrationTest { input.getBytes()); context.restoreAuthSystemState(); MvcResult mvcResult = getClient(token) - .perform(MockMvcRequestBuilders.fileUpload("/api/core/items/" + item.getID() + "/bitstreams") - .file(file)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.bundleName", is("ORIGINAL"))) - .andExpect(jsonPath("$.name", is("hello.txt"))) - .andExpect(jsonPath("$.sequenceId", is(1))) - .andReturn(); + .perform(MockMvcRequestBuilders.fileUpload("/api/core/bundles/" + bundle.getID() + "/bitstreams") + .file(file)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.bundleName", is("TESTINGBUNDLE"))) + .andExpect(jsonPath("$.name", is("hello.txt"))) + .andExpect(jsonPath("$.sequenceId", is(1))) + .andReturn(); ObjectMapper mapper = new ObjectMapper(); @@ -172,10 +180,10 @@ public class ItemUploadControllerIT extends AbstractEntityIntegrationTest { Map map = mapper.readValue(content, Map.class); String bitstreamId = String.valueOf(map.get("id")); - getClient(token).perform(get("/api/core/items/" + item.getID() + "/bitstreams")) + getClient(token).perform(get("/api/core/bundles/" + bundle.getID() + "/bitstreams")) .andExpect(status().isOk()) .andExpect(jsonPath("_embedded.bitstreams", Matchers.hasItem( - BitstreamMatcher.matchBitstreamEntry(UUID.fromString(bitstreamId), file.getSize())))); + BitstreamMatcher.matchBitstreamEntry(UUID.fromString(bitstreamId), file.getSize())))); } @Test @@ -190,15 +198,18 @@ public class ItemUploadControllerIT extends AbstractEntityIntegrationTest { .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 item = ItemBuilder.createItem(context, col1) .withTitle("Author1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald") .build(); + Bundle bundle = BundleBuilder.createBundle(context, item) + .withName("TESTINGBUNDLE") + .build(); context.setCurrentUser(eperson); + authorizeService.addPolicy(context, bundle, Constants.ADD, eperson); + authorizeService.addPolicy(context, bundle, Constants.WRITE, eperson); authorizeService.addPolicy(context, item, Constants.ADD, eperson); authorizeService.addPolicy(context, item, Constants.WRITE, eperson); String token = getAuthToken(eperson.getEmail(), password); @@ -210,10 +221,10 @@ public class ItemUploadControllerIT extends AbstractEntityIntegrationTest { context.restoreAuthSystemState(); MvcResult mvcResult = getClient(token) - .perform(MockMvcRequestBuilders.fileUpload("/api/core/items/" + item.getID() + "/bitstreams") - .file(file)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.uuid", notNullValue())).andReturn(); + .perform(MockMvcRequestBuilders.fileUpload("/api/core/bundles/" + bundle.getID() + "/bitstreams") + .file(file)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.uuid", notNullValue())).andReturn(); ObjectMapper mapper = new ObjectMapper(); @@ -221,10 +232,10 @@ public class ItemUploadControllerIT extends AbstractEntityIntegrationTest { Map map = mapper.readValue(content, Map.class); String bitstreamId = String.valueOf(map.get("id")); - getClient(token).perform(get("/api/core/items/" + item.getID() + "/bitstreams")) + getClient(token).perform(get("/api/core/bundles/" + bundle.getID() + "/bitstreams")) .andExpect(status().isOk()) .andExpect(jsonPath("_embedded.bitstreams", Matchers.hasItem( - BitstreamMatcher.matchBitstreamEntry(UUID.fromString(bitstreamId), file.getSize())))); + BitstreamMatcher.matchBitstreamEntry(UUID.fromString(bitstreamId), file.getSize())))); } @Test @@ -239,14 +250,16 @@ public class ItemUploadControllerIT extends AbstractEntityIntegrationTest { .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 item = ItemBuilder.createItem(context, col1) .withTitle("Author1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald") .build(); + Bundle bundle = BundleBuilder.createBundle(context, item) + .withName("TESTINGBUNDLE") + .build(); + context.setCurrentUser(eperson); String token = getAuthToken(eperson.getEmail(), password); @@ -256,11 +269,12 @@ public class ItemUploadControllerIT extends AbstractEntityIntegrationTest { input.getBytes()); context.restoreAuthSystemState(); - getClient(token).perform(MockMvcRequestBuilders.fileUpload("/api/core/items/" + item.getID() + "/bitstreams") - .file(file)) + getClient(token).perform(MockMvcRequestBuilders + .fileUpload("/api/core/bundles/" + bundle.getID() + "/bitstreams") + .file(file)) .andExpect(status().isForbidden()); - getClient(token).perform(get("/api/core/items/" + item.getID() + "/bitstreams")) + getClient(token).perform(get("/api/core/bundles/" + bundle.getID() + "/bitstreams")) .andExpect(status().isOk()) .andExpect(jsonPath("_embedded.bitstreams").doesNotExist()); } @@ -277,14 +291,17 @@ public class ItemUploadControllerIT extends AbstractEntityIntegrationTest { .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 item = ItemBuilder.createItem(context, col1) .withTitle("Author1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald") .build(); + + Bundle bundle = BundleBuilder.createBundle(context, item) + .withName("TESTINGBUNDLE") + .build(); + context.setCurrentUser(eperson); String token = getAuthToken(eperson.getEmail(), password); @@ -294,11 +311,11 @@ public class ItemUploadControllerIT extends AbstractEntityIntegrationTest { input.getBytes()); context.restoreAuthSystemState(); - getClient().perform(MockMvcRequestBuilders.fileUpload("/api/core/items/" + item.getID() + "/bitstreams") + getClient().perform(MockMvcRequestBuilders.fileUpload("/api/core/bundles/" + bundle.getID() + "/bitstreams") .file(file)) .andExpect(status().isUnauthorized()); - getClient(token).perform(get("/api/core/items/" + item.getID() + "/bitstreams")) + getClient(token).perform(get("/api/core/bundles/" + bundle.getID() + "/bitstreams")) .andExpect(status().isOk()) .andExpect(jsonPath("_embedded.bitstreams").doesNotExist()); } @@ -315,15 +332,20 @@ public class ItemUploadControllerIT extends AbstractEntityIntegrationTest { .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 item = ItemBuilder.createItem(context, col1) .withTitle("Author1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald") .build(); + + Bundle bundle = BundleBuilder.createBundle(context, item) + .withName("TESTINGBUNDLE") + .build(); + context.setCurrentUser(eperson); + authorizeService.addPolicy(context, bundle, Constants.ADD, eperson); + authorizeService.addPolicy(context, bundle, Constants.WRITE, eperson); authorizeService.addPolicy(context, item, Constants.ADD, eperson); authorizeService.addPolicy(context, item, Constants.WRITE, eperson); String token = getAuthToken(eperson.getEmail(), password); @@ -334,31 +356,29 @@ public class ItemUploadControllerIT extends AbstractEntityIntegrationTest { input.getBytes()); BitstreamRest bitstreamRest = new BitstreamRest(); - String testbundle = "TESTBUNDLE"; - bitstreamRest.setBundleName(testbundle); ObjectMapper mapper = new ObjectMapper(); context.restoreAuthSystemState(); MvcResult mvcResult = getClient(token) - .perform(MockMvcRequestBuilders.fileUpload("/api/core/items/" + item.getID() + "/bitstreams") - .file(file) - .param("properties", mapper - .writeValueAsString(bitstreamRest))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.bundleName", is(testbundle))) - .andExpect(jsonPath("$.uuid", notNullValue())).andReturn(); + .perform(MockMvcRequestBuilders.fileUpload("/api/core/bundles/" + bundle.getID() + "/bitstreams") + .file(file) + .param("properties", mapper + .writeValueAsString(bitstreamRest))) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.bundleName", is("TESTINGBUNDLE"))) + .andExpect(jsonPath("$.uuid", notNullValue())).andReturn(); String content = mvcResult.getResponse().getContentAsString(); Map map = mapper.readValue(content, Map.class); String bitstreamId = String.valueOf(map.get("id")); - getClient(token).perform(get("/api/core/items/" + item.getID() + "/bitstreams")) + getClient(token).perform(get("/api/core/bundles/" + bundle.getID() + "/bitstreams")) .andExpect(status().isOk()) .andExpect(jsonPath("_embedded.bitstreams", Matchers.hasItem( - BitstreamMatcher.matchBitstreamEntry(UUID.fromString(bitstreamId), file.getSize())))); + BitstreamMatcher.matchBitstreamEntry(UUID.fromString(bitstreamId), file.getSize())))); } // TODO This test doesn't work either as it seems that only the first file is ever transfered into the request @@ -376,8 +396,6 @@ public class ItemUploadControllerIT extends AbstractEntityIntegrationTest { .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 item = ItemBuilder.createItem(context, col1) .withTitle("Author1") @@ -385,6 +403,10 @@ public class ItemUploadControllerIT extends AbstractEntityIntegrationTest { .withAuthor("Smith, Donald") .build(); + Bundle bundle = BundleBuilder.createBundle(context, item) + .withName("TESTINGBUNDLE") + .build(); + String token = getAuthToken(admin.getEmail(), password); String input = "Hello, World!"; @@ -393,56 +415,10 @@ public class ItemUploadControllerIT extends AbstractEntityIntegrationTest { MockMultipartFile file2 = new MockMultipartFile("file", "hello2.txt", MediaType.TEXT_PLAIN_VALUE, input.getBytes()); context.restoreAuthSystemState(); - getClient(token).perform(MockMvcRequestBuilders.fileUpload("/api/core/items/" + item.getID() + "/bitstreams") - .file(file).file(file2)) - .andExpect(status().isUnprocessableEntity()); - } - - @Test - public void uploadBitstreamNoBundleNameInPropertiesUnprocessableException() 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 item = ItemBuilder.createItem(context, col1) - .withTitle("Author1") - .withIssueDate("2017-10-17") - .withAuthor("Smith, Donald") - .build(); - context.setCurrentUser(eperson); - authorizeService.addPolicy(context, item, Constants.ADD, eperson); - authorizeService.addPolicy(context, item, Constants.WRITE, eperson); - String token = getAuthToken(eperson.getEmail(), password); - - String input = "Hello, World!"; - - MockMultipartFile file = new MockMultipartFile("file", "hello.txt", MediaType.TEXT_PLAIN_VALUE, - input.getBytes()); - - BitstreamRest bitstreamRest = new BitstreamRest(); - - ObjectMapper mapper = new ObjectMapper(); - - - context.restoreAuthSystemState(); - getClient(token).perform(MockMvcRequestBuilders.fileUpload("/api/core/items/" + item.getID() + "/bitstreams") - .file(file) - .param("properties", mapper - .writeValueAsString(bitstreamRest))) - .andExpect(status().isUnprocessableEntity()); - - getClient(token).perform(get("/api/core/items/" + item.getID() + "/bitstreams")) - .andExpect(status().isOk()) - .andExpect(jsonPath("_embedded.bitstreams").doesNotExist()); + getClient(token) + .perform(MockMvcRequestBuilders.fileUpload("/api/core/bundles/" + bundle.getID() + "/bitstreams") + .file(file).file(file2)) + .andExpect(status().isUnprocessableEntity()); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java index ac019ef2e7..06a584063e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java @@ -24,12 +24,14 @@ import java.util.UUID; import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.rest.builder.CollectionBuilder; import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.converter.CollectionConverter; +import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.matcher.CollectionMatcher; import org.dspace.app.rest.matcher.MetadataMatcher; +import org.dspace.app.rest.matcher.PageMatcher; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.MetadataRest; import org.dspace.app.rest.model.MetadataValueRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.authorize.service.AuthorizeService; @@ -45,7 +47,7 @@ import org.springframework.http.MediaType; public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired - CollectionConverter collectionConverter; + ConverterService converter; @Autowired AuthorizeService authorizeService; @@ -369,7 +371,7 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes ObjectMapper mapper = new ObjectMapper(); - CollectionRest collectionRest = collectionConverter.fromModel(col1); + CollectionRest collectionRest = converter.toRest(col1, Projection.DEFAULT); collectionRest.setMetadata(new MetadataRest() .put("dc.title", new MetadataValueRest("Electronic theses and dissertations"))); @@ -730,7 +732,7 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes String token = getAuthToken(eperson.getEmail(), password); ObjectMapper mapper = new ObjectMapper(); - CollectionRest collectionRest = collectionConverter.fromModel(col1); + CollectionRest collectionRest = converter.toRest(col1, Projection.DEFAULT); collectionRest.setMetadata(new MetadataRest() .put("dc.title", new MetadataValueRest("Electronic theses and dissertations"))); @@ -855,4 +857,39 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isBadRequest()); } + + + @Test + public void findAllCollectionsWithMultilanguageTitlesTest() throws Exception { + + //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. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Community child2 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community Two") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withNameForLanguage("Collection 1", "en") + .withNameForLanguage("Col 1", "fr") + .withNameForLanguage("Coll 1", "de") + .build(); + Collection col2 = CollectionBuilder.createCollection(context, child2).withName("Collection 2").build(); + + + getClient().perform(get("/api/core/collections")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.collections", Matchers.containsInAnyOrder( + CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle()), + CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle()) + ))) + .andExpect(jsonPath("$.page", PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 2))); + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java index cdf0c203e8..b3b8c6c22e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java @@ -30,16 +30,16 @@ import java.util.stream.Collectors; import java.util.stream.StreamSupport; import com.fasterxml.jackson.databind.ObjectMapper; - import org.dspace.app.rest.builder.CollectionBuilder; import org.dspace.app.rest.builder.CommunityBuilder; -import org.dspace.app.rest.converter.CommunityConverter; +import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.matcher.CommunityMatcher; import org.dspace.app.rest.matcher.MetadataMatcher; import org.dspace.app.rest.matcher.PageMatcher; import org.dspace.app.rest.model.CommunityRest; import org.dspace.app.rest.model.MetadataRest; import org.dspace.app.rest.model.MetadataValueRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.authorize.service.AuthorizeService; @@ -62,7 +62,7 @@ import org.springframework.test.web.servlet.MvcResult; public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired - CommunityConverter communityConverter; + ConverterService converter; @Autowired CommunityService communityService; @@ -831,7 +831,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest ObjectMapper mapper = new ObjectMapper(); - CommunityRest communityRest = communityConverter.fromModel(parentCommunity); + CommunityRest communityRest = converter.toRest(parentCommunity, Projection.DEFAULT); communityRest.setMetadata(new MetadataRest() .put("dc.title", new MetadataValueRest("Electronic theses and dissertations"))); @@ -1041,7 +1041,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest ObjectMapper mapper = new ObjectMapper(); - CommunityRest communityRest = communityConverter.fromModel(parentCommunity); + CommunityRest communityRest = converter.toRest(parentCommunity, Projection.DEFAULT); communityRest.setMetadata(new MetadataRest() .put("dc.title", new MetadataValueRest("Electronic theses and dissertations"))); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index 747ea85ff7..717a9e5b9e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -317,11 +317,11 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { Matchers.containsString("/api/core/items"))) ; - getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/bitstreams")) + getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/bundles")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._links.self.href", Matchers - .containsString("/api/core/items/" + publicItem1.getID() + "/bitstreams"))) + .containsString("/api/core/items/" + publicItem1.getID() + "/bundles"))) ; getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/owningCollection")) @@ -1156,12 +1156,12 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { getClient().perform(get("/api/core/items/" + publicItem.getID())) .andExpect(status().isOk()); - // Check publicItem bitstream creatino - getClient().perform(get("/api/core/items/" + publicItem.getID() + "/bitstreams")) + // Check publicItem bitstream creation (shuold be stored in bundle) + getClient().perform(get("/api/core/items/" + publicItem.getID() + "/bundles")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._links.self.href", Matchers - .containsString("/api/core/items/" + publicItem.getID() + "/bitstreams"))); + .containsString("/api/core/items/" + publicItem.getID() + "/bundles"))); String token = getAuthToken(admin.getEmail(), password); @@ -1850,4 +1850,61 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isBadRequest()); } + @Test + public void updateTestEPersonWithoutPermissionForbidden() throws Exception { + //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 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(); + + context.restoreAuthSystemState(); + + ObjectMapper mapper = new ObjectMapper(); + ItemRest itemRest = new ItemRest(); + itemRest.setName("Practices of research data curation in institutional repositories:" + + " A qualitative view from repository staff"); + itemRest.setInArchive(true); + itemRest.setDiscoverable(true); + itemRest.setWithdrawn(false); + + + String token = getAuthToken(admin.getEmail(), password); + MvcResult mvcResult = getClient(token).perform(post("/api/core/items?owningCollection=" + + col1.getID().toString()) + .content(mapper.writeValueAsBytes(itemRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andReturn(); + + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String itemUuidString = String.valueOf(map.get("uuid")); + String itemHandleString = String.valueOf(map.get("handle")); + + itemRest.setMetadata(new MetadataRest() + .put("dc.description", new MetadataValueRest("

Some cool HTML code here

")) + .put("dc.description.abstract", + new MetadataValueRest("Sample item created via the REST API")) + .put("dc.description.tableofcontents", new MetadataValueRest("

HTML News

")) + .put("dc.rights", new MetadataValueRest("New Custom Copyright Text")) + .put("dc.title", new MetadataValueRest("New title"))); + + itemRest.setUuid(itemUuidString); + itemRest.setHandle(itemHandleString); + + token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(put("/api/core/items/" + itemUuidString) + .content(mapper.writeValueAsBytes(itemRest)) + .contentType(contentType)) + .andExpect(status().isForbidden()); + } + + } \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java index 8ffcd4df67..9aa2820eae 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java @@ -22,9 +22,10 @@ import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.rest.builder.MetadataSchemaBuilder; -import org.dspace.app.rest.converter.MetadataSchemaConverter; +import org.dspace.app.rest.converter.ConverterService; import org.dspace.app.rest.matcher.MetadataschemaMatcher; import org.dspace.app.rest.model.MetadataSchemaRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.content.MetadataSchema; import org.hamcrest.Matchers; @@ -45,7 +46,7 @@ public class MetadataSchemaRestRepositoryIT extends AbstractControllerIntegratio private static final String TEST_NAMESPACE_UPDATED = "testSchemaNameSpaceUpdated"; @Autowired - MetadataSchemaConverter metadataSchemaConverter; + ConverterService converter; @Test public void findAll() throws Exception { @@ -88,7 +89,7 @@ public class MetadataSchemaRestRepositoryIT extends AbstractControllerIntegratio .build(); context.restoreAuthSystemState(); - MetadataSchemaRest metadataSchemaRest = metadataSchemaConverter.fromModel(metadataSchema); + MetadataSchemaRest metadataSchemaRest = converter.toRest(metadataSchema, Projection.DEFAULT); metadataSchemaRest.setPrefix(TEST_NAME); metadataSchemaRest.setNamespace(TEST_NAMESPACE); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProcessRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProcessRestRepositoryIT.java new file mode 100644 index 0000000000..92c37007b1 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ProcessRestRepositoryIT.java @@ -0,0 +1,215 @@ +/** + * 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 java.sql.SQLException; +import java.util.LinkedList; + +import org.apache.commons.collections4.CollectionUtils; +import org.dspace.app.rest.builder.ProcessBuilder; +import org.dspace.app.rest.matcher.PageMatcher; +import org.dspace.app.rest.matcher.ProcessMatcher; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.content.ProcessStatus; +import org.dspace.scripts.DSpaceCommandLineParameter; +import org.dspace.scripts.Process; +import org.dspace.scripts.service.ProcessService; +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +public class ProcessRestRepositoryIT extends AbstractControllerIntegrationTest { + + @Autowired + private ProcessService processService; + + Process process; + + LinkedList parameters = new LinkedList<>(); + + + @Before + public void setup() throws SQLException { + parameters.add(new DSpaceCommandLineParameter("-r", "test")); + parameters.add(new DSpaceCommandLineParameter("-i", null)); + + process = ProcessBuilder.createProcess(context, admin, "mock-script", parameters).build(); + } + + @Test + public void getProcessAdmin() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(get("/api/system/processes/" + process.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + ProcessMatcher.matchProcess(process.getName(), String.valueOf(process.getEPerson().getID()), + process.getID(), parameters, ProcessStatus.SCHEDULED))) + ); + } + + @Test + public void getProcessAdminEmptyParam() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + + Process process = ProcessBuilder.createProcess(context, admin, "mock-script", new LinkedList<>()).build(); + + getClient(token).perform(get("/api/system/processes/" + process.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + ProcessMatcher.matchProcess(process.getName(), String.valueOf(process.getEPerson().getID()), + process.getID(), new LinkedList<>(), ProcessStatus.SCHEDULED))) + ); + } + + @Test + public void getProcessAnonymousUnauthorizedException() throws Exception { + + getClient().perform(get("/api/system/processes/" + process.getID())) + .andExpect(status().isUnauthorized()); + } + + @Test + public void getProcessForStartedUser() throws Exception { + Process newProcess = ProcessBuilder.createProcess(context, eperson, "mock-script", new LinkedList<>()).build(); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(get("/api/system/processes/" + newProcess.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.is( + ProcessMatcher.matchProcess(newProcess.getName(), + String.valueOf(newProcess.getEPerson().getID()), + newProcess.getID(), + new LinkedList<>(), + ProcessStatus.SCHEDULED)))); + + } + + @Test + public void getProcessForDifferentUserForbiddenException() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(get("/api/system/processes/" + process.getID())) + .andExpect(status().isForbidden()); + + } + + @Test + public void getProcessNotExisting() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(get("/api/system/processes/" + process.getID() * 23 + 17)) + .andExpect(status().isNotFound()); + } + + @Test + public void getAllProcessesTestAnonymous() throws Exception { + + + getClient().perform(get("/api/system/processes/")) + .andExpect(status().isUnauthorized()); + } + + + @Test + public void getAllProcessesTestAdmin() throws Exception { + + Process newProcess = ProcessBuilder.createProcess(context, eperson, "mock-script", parameters).build(); + Process newProcess1 = ProcessBuilder.createProcess(context, eperson, "mock-script", parameters).build(); + Process newProcess2 = ProcessBuilder.createProcess(context, eperson, "mock-script", parameters).build(); + Process newProcess3 = ProcessBuilder.createProcess(context, eperson, "mock-script", parameters).build(); + Process newProcess4 = ProcessBuilder.createProcess(context, eperson, "mock-script", parameters).build(); + Process newProcess5 = ProcessBuilder.createProcess(context, eperson, "mock-script", parameters).build(); + Process newProcess6 = ProcessBuilder.createProcess(context, eperson, "mock-script", parameters).build(); + Process newProcess7 = ProcessBuilder.createProcess(context, admin, "mock-script", parameters).build(); + Process newProcess8 = ProcessBuilder.createProcess(context, admin, "mock-script", parameters).build(); + Process newProcess9 = ProcessBuilder.createProcess(context, admin, "mock-script", parameters).build(); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(get("/api/system/processes/")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.processes", containsInAnyOrder( + ProcessMatcher.matchProcess(process.getName(), String.valueOf(process.getEPerson().getID()), + process.getID(), parameters, ProcessStatus.SCHEDULED), + ProcessMatcher.matchProcess(newProcess.getName(), + String.valueOf(newProcess.getEPerson().getID()), + newProcess.getID(), parameters, ProcessStatus.SCHEDULED), + ProcessMatcher.matchProcess(newProcess1.getName(), + String.valueOf(newProcess1.getEPerson().getID()), + newProcess1.getID(), parameters, ProcessStatus.SCHEDULED), + ProcessMatcher.matchProcess(newProcess2.getName(), + String.valueOf(newProcess2.getEPerson().getID()), + newProcess2.getID(), parameters, ProcessStatus.SCHEDULED), + ProcessMatcher.matchProcess(newProcess3.getName(), + String.valueOf(newProcess3.getEPerson().getID()), + newProcess3.getID(), parameters, ProcessStatus.SCHEDULED), + ProcessMatcher.matchProcess(newProcess4.getName(), + String.valueOf(newProcess4.getEPerson().getID()), + newProcess4.getID(), parameters, ProcessStatus.SCHEDULED), + ProcessMatcher.matchProcess(newProcess5.getName(), + String.valueOf(newProcess5.getEPerson().getID()), + newProcess5.getID(), parameters, ProcessStatus.SCHEDULED), + ProcessMatcher.matchProcess(newProcess6.getName(), + String.valueOf(newProcess6.getEPerson().getID()), + newProcess6.getID(), parameters, ProcessStatus.SCHEDULED), + ProcessMatcher.matchProcess(newProcess7.getName(), + String.valueOf(newProcess7.getEPerson().getID()), + newProcess7.getID(), parameters, ProcessStatus.SCHEDULED), + ProcessMatcher.matchProcess(newProcess8.getName(), + String.valueOf(newProcess8.getEPerson().getID()), + newProcess8.getID(), parameters, ProcessStatus.SCHEDULED), + ProcessMatcher.matchProcess(newProcess9.getName(), + String.valueOf(newProcess9.getEPerson().getID()), + newProcess9.getID(), parameters, ProcessStatus.SCHEDULED) + ))) + .andExpect(jsonPath("$.page", is( + PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 1, 11)))); + + } + + @Test + public void getAllProcessesTestStartingUser() throws Exception { + Process newProcess = ProcessBuilder.createProcess(context, eperson, "mock-script", new LinkedList<>()).build(); + Process newProcess1 = ProcessBuilder.createProcess(context, eperson, "mock-script", new LinkedList<>()).build(); + Process newProcess2 = ProcessBuilder.createProcess(context, eperson, "mock-script", new LinkedList<>()).build(); + Process newProcess3 = ProcessBuilder.createProcess(context, eperson, "mock-script", new LinkedList<>()).build(); + Process newProcess4 = ProcessBuilder.createProcess(context, eperson, "mock-script", new LinkedList<>()).build(); + Process newProcess5 = ProcessBuilder.createProcess(context, eperson, "mock-script", new LinkedList<>()).build(); + Process newProcess6 = ProcessBuilder.createProcess(context, eperson, "mock-script", new LinkedList<>()).build(); + Process newProcess7 = ProcessBuilder.createProcess(context, admin, "mock-script", new LinkedList<>()).build(); + Process newProcess8 = ProcessBuilder.createProcess(context, admin, "mock-script", new LinkedList<>()).build(); + Process newProcess9 = ProcessBuilder.createProcess(context, admin, "mock-script", new LinkedList<>()).build(); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(get("/api/system/processes/")) + .andExpect(status().isForbidden()); + } + + @After + public void destroy() throws Exception { + CollectionUtils.emptyIfNull(processService.findAll(context)).stream().forEach(process -> { + try { + processService.delete(context, process); + } catch (SQLException e) { + throw new RuntimeException(e); + } + }); + super.destroy(); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java index 4a4f16864f..35693e27e5 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipRestRepositoryIT.java @@ -115,6 +115,8 @@ public class RelationshipRestRepositoryIT extends AbstractEntityIntegrationTest .withTitle("Author1") .withIssueDate("2017-10-17") .withAuthor("Smith, Donald") + .withPersonIdentifierLastName("Smith") + .withPersonIdentifierFirstName("Donald") .withRelationshipType("Person") .build(); @@ -934,6 +936,12 @@ public class RelationshipRestRepositoryIT extends AbstractEntityIntegrationTest context.turnOffAuthorisationSystem(); + Item publication1 = ItemBuilder.createItem(context, col3) + .withTitle("Publication1") + .withIssueDate("2015-01-01") + .withRelationshipType("Publication") + .build(); + Item author2 = ItemBuilder.createItem(context, col2) .withTitle("Author2") .withIssueDate("2016-02-13") @@ -1144,6 +1152,12 @@ public class RelationshipRestRepositoryIT extends AbstractEntityIntegrationTest context.turnOffAuthorisationSystem(); + Item publication1 = ItemBuilder.createItem(context, col3) + .withTitle("Publication1") + .withIssueDate("2015-01-01") + .withRelationshipType("Publication") + .build(); + Item author2 = ItemBuilder.createItem(context, col2) .withTitle("Author2") .withIssueDate("2016-02-13") diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestControllerIT.java index 4777ab0964..b0a269f4e1 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RelationshipTypeRestControllerIT.java @@ -184,6 +184,7 @@ public class RelationshipTypeRestControllerIT extends AbstractEntityIntegrationT .createRelationshipBuilder(context, publication2, author3, isAuthorOfPublicationRelationshipType).build(); context.restoreAuthSystemState(); + //verify results getClient().perform(get("/api/core/relationships/search/byLabel?label=isAuthorOfPublication")) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.relationships", containsInAnyOrder( @@ -193,6 +194,12 @@ public class RelationshipTypeRestControllerIT extends AbstractEntityIntegrationT RelationshipMatcher.matchRelationship(relationship4) ))); + //verify paging + getClient().perform(get("/api/core/relationships/search/byLabel?label=isAuthorOfPublication&size=2")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(4))); + + //verify results getClient().perform(get("/api/core/relationships/search/byLabel?label=isAuthorOfPublication&dso=" + publication.getID())) .andExpect(status().isOk()) @@ -201,6 +208,13 @@ public class RelationshipTypeRestControllerIT extends AbstractEntityIntegrationT RelationshipMatcher.matchRelationship(relationship2) ))); + //verify paging + getClient().perform(get("/api/core/relationships/search/byLabel?label=isAuthorOfPublication&dso=" + + publication.getID() + "&size=1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + //verify results getClient().perform(get("/api/core/relationships/search/byLabel?label=isAuthorOfPublication&dso=" + publication2.getID())) .andExpect(status().isOk()) @@ -208,5 +222,12 @@ public class RelationshipTypeRestControllerIT extends AbstractEntityIntegrationT RelationshipMatcher.matchRelationship(relationship3), RelationshipMatcher.matchRelationship(relationship4) ))); + + //verify paging + getClient().perform(get("/api/core/relationships/search/byLabel?label=isAuthorOfPublication&dso=" + + publication2.getID() + "&size=1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(2))); + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java new file mode 100644 index 0000000000..930f3ab4d7 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ScriptRestRepositoryIT.java @@ -0,0 +1,258 @@ +/** + * 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.hasItem; +import static org.hamcrest.Matchers.is; +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.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.sql.SQLException; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +import com.google.gson.Gson; +import org.apache.commons.collections4.CollectionUtils; +import org.dspace.app.rest.converter.DSpaceRunnableParameterConverter; +import org.dspace.app.rest.matcher.PageMatcher; +import org.dspace.app.rest.matcher.ProcessMatcher; +import org.dspace.app.rest.matcher.ScriptMatcher; +import org.dspace.app.rest.model.ParameterValueRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.content.ProcessStatus; +import org.dspace.scripts.DSpaceCommandLineParameter; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.scripts.service.ProcessService; +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { + + @Autowired + private ProcessService processService; + + @Autowired + private List dSpaceRunnableList; + + @Autowired + private DSpaceRunnableParameterConverter dSpaceRunnableParameterConverter; + + @Test + public void findAllScriptsTest() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(get("/api/system/scripts")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.scripts", containsInAnyOrder( + ScriptMatcher.matchScript(dSpaceRunnableList.get(0).getName(), + dSpaceRunnableList.get(0).getDescription()), + ScriptMatcher.matchScript(dSpaceRunnableList.get(1).getName(), + dSpaceRunnableList.get(1).getDescription()) + ))); + + } + + + @Test + public void findAllScriptsUnauthorizedTest() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(get("/api/system/scripts")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page", + is(PageMatcher.pageEntryWithTotalPagesAndElements(0, 20, 0, 0)))); + + } + + @Test + public void findAllScriptsPaginationTest() throws Exception { + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(get("/api/system/scripts").param("size", "1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.scripts", hasItem( + ScriptMatcher.matchScript(dSpaceRunnableList.get(0).getName(), + dSpaceRunnableList.get(0).getDescription()) + ))) + .andExpect(jsonPath("$._embedded.scripts", Matchers.not(hasItem( + ScriptMatcher.matchScript(dSpaceRunnableList.get(1).getName(), + dSpaceRunnableList.get(1).getDescription()) + )))) + .andExpect(jsonPath("$.page", + is(PageMatcher.pageEntry(0, 1)))); + + + getClient(token).perform(get("/api/system/scripts").param("size", "1").param("page", "1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.scripts", Matchers.not(hasItem( + ScriptMatcher.matchScript(dSpaceRunnableList.get(0).getName(), + dSpaceRunnableList.get(0).getDescription()) + )))) + .andExpect(jsonPath("$._embedded.scripts", hasItem( + ScriptMatcher.matchScript(dSpaceRunnableList.get(1).getName(), + dSpaceRunnableList.get(1).getDescription()) + ))) + .andExpect(jsonPath("$.page", + is(PageMatcher.pageEntry(1, 1)))); + } + + @Test + public void findOneScriptByNameTest() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(get("/api/system/scripts/mock-script")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", ScriptMatcher + .matchMockScript(dSpaceRunnableList.get(1).getOptions()))); + } + + @Test + public void findOneScriptByNameTestAccessDenied() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(get("/api/system/scripts/mock-script")) + .andExpect(status().isForbidden()); + } + + @Test + public void findOneScriptByInvalidNameBadRequestExceptionTest() throws Exception { + getClient().perform(get("/api/system/scripts/mock-script-invalid")) + .andExpect(status().isBadRequest()); + } + + @Test + public void postProcessNonAdminAuthorizeException() throws Exception { + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(post("/api/system/scripts/mock-script/processes").contentType("multipart/form-data")) + .andExpect(status().isForbidden()); + } + + @Test + public void postProcessAnonymousAuthorizeException() throws Exception { + getClient().perform(post("/api/system/scripts/mock-script/processes").contentType("multipart/form-data")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void postProcessAdminWrongOptionsException() throws Exception { + + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(post("/api/system/scripts/mock-script/processes").contentType("multipart/form-data")) + .andExpect(status().isAccepted()) + .andExpect(jsonPath("$", is( + ProcessMatcher.matchProcess("mock-script", + String.valueOf(admin.getID()), new LinkedList<>(), + ProcessStatus.FAILED)))); + } + + @Test + public void postProcessAdminNoOptionsFailedStatus() throws Exception { + +// List list = new LinkedList<>(); +// +// ParameterValueRest parameterValueRest = new ParameterValueRest(); +// parameterValueRest.setName("-z"); +// parameterValueRest.setValue("test"); +// ParameterValueRest parameterValueRest1 = new ParameterValueRest(); +// parameterValueRest1.setName("-q"); +// list.add(parameterValueRest); +// list.add(parameterValueRest1); + + LinkedList parameters = new LinkedList<>(); + + parameters.add(new DSpaceCommandLineParameter("-z", "test")); + parameters.add(new DSpaceCommandLineParameter("-q", null)); + + List list = parameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)).collect(Collectors.toList()); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(post("/api/system/scripts/mock-script/processes").contentType("multipart/form-data") + .param("properties", + new Gson().toJson(list))) + .andExpect(status().isAccepted()) + .andExpect(jsonPath("$", is( + ProcessMatcher.matchProcess("mock-script", + String.valueOf(admin.getID()), parameters, + ProcessStatus.FAILED)))); + } + + @Test + public void postProcessNonExistingScriptNameException() throws Exception { + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(post("/api/system/scripts/mock-script-invalid/processes") + .contentType("multipart/form-data")) + .andExpect(status().isBadRequest()); + } + + @Test + public void postProcessAdminWithOptionsSuccess() throws Exception { + LinkedList parameters = new LinkedList<>(); + + parameters.add(new DSpaceCommandLineParameter("-r", "test")); + parameters.add(new DSpaceCommandLineParameter("-i", null)); + + List list = parameters.stream() + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)).collect(Collectors.toList()); + + String token = getAuthToken(admin.getEmail(), password); + List acceptableProcessStatuses = new LinkedList<>(); + acceptableProcessStatuses.addAll(Arrays.asList(ProcessStatus.SCHEDULED, + ProcessStatus.RUNNING, + ProcessStatus.COMPLETED)); + + getClient(token).perform(post("/api/system/scripts/mock-script/processes").contentType("multipart/form-data") + .param("properties", + new Gson().toJson(list))) + .andExpect(status().isAccepted()) + .andExpect(jsonPath("$", is( + ProcessMatcher.matchProcess("mock-script", + String.valueOf(admin.getID()), + parameters, + acceptableProcessStatuses)))); + + } + + @Test + public void postProcessAdminWithWrongContentTypeBadRequestException() throws Exception { + + String token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(post("/api/system/scripts/mock-script-invalid/processes")) + .andExpect(status().isBadRequest()); + } + + @After + public void destroy() throws Exception { + CollectionUtils.emptyIfNull(processService.findAll(context)).stream().forEach(process -> { + try { + processService.delete(context, process); + } catch (SQLException e) { + throw new RuntimeException(e); + } + }); + super.destroy(); + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java index 26252b3731..756d86d798 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionFormsControllerIT.java @@ -78,7 +78,7 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe // check the first two rows .andExpect(jsonPath("$.rows[0].fields", contains( SubmissionFormFieldMatcher.matchFormFieldDefinition("name", "Author", - null, true, "Add an author", "dc.contributor.author")))) + null, true,"Add an author", "dc.contributor.author")))) .andExpect(jsonPath("$.rows[1].fields", contains( SubmissionFormFieldMatcher.matchFormFieldDefinition("onebox", "Title", "You must enter a main title for this item.", false, @@ -90,9 +90,9 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe "You must enter at least the year.", false, "Please give the date", "col-sm-4", "dc.date.issued"), - SubmissionFormFieldMatcher.matchFormFieldDefinition("onebox", "Publisher", null, false, - "Enter the name of", "col-sm-8", - "dc.publisher")))) + SubmissionFormFieldMatcher.matchFormFieldDefinition("onebox", "Publisher", + null, false,"Enter the name of", + "col-sm-8","dc.publisher")))) ; } @@ -114,9 +114,9 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe // check the first two rows .andExpect(jsonPath("$.rows[0].fields", contains( SubmissionFormFieldMatcher.matchFormOpenRelationshipFieldDefinition("name", - "Author", null, true, "Add an author", + "Author", null, true,"Add an author", "dc.contributor.author", "isAuthorOfPublication", null, - "personConfiguration")))) + "personConfiguration", true)))) ; } @@ -138,8 +138,8 @@ public class SubmissionFormsControllerIT extends AbstractControllerIntegrationTe // check the first two rows .andExpect(jsonPath("$.rows[0].fields", contains( SubmissionFormFieldMatcher.matchFormClosedRelationshipFieldDefinition("Journal", null, - false, "Select the journal related to this volume.", "isVolumeOfJournal", - "creativework.publisher:somepublishername", "periodicalConfiguration")))) + false,"Select the journal related to this volume.", "isVolumeOfJournal", + "creativework.publisher:somepublishername", "periodicalConfiguration", false)))) ; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/AbstractBuilder.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/AbstractBuilder.java index 96d0c48c72..403e623c3c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/AbstractBuilder.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/AbstractBuilder.java @@ -7,11 +7,13 @@ */ package org.dspace.app.rest.builder; +import java.sql.SQLException; 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.AuthorizeException; import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.ResourcePolicyService; @@ -37,6 +39,8 @@ import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.GroupService; import org.dspace.eperson.service.RegistrationDataService; +import org.dspace.scripts.factory.ScriptServiceFactory; +import org.dspace.scripts.service.ProcessService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.versioning.factory.VersionServiceFactory; import org.dspace.versioning.service.VersionHistoryService; @@ -51,6 +55,8 @@ import org.dspace.xmlworkflow.storedcomponents.service.XmlWorkflowItemService; /** * Abstract builder class that holds references to all available services * + * @param This param represents the Model object for the Builder + * @param This param represents the Service object for the builder * @author Jonas Van Goolen - (jonas@atmire.com) */ public abstract class AbstractBuilder { @@ -82,6 +88,7 @@ public abstract class AbstractBuilder { static RelationshipService relationshipService; static RelationshipTypeService relationshipTypeService; static EntityTypeService entityTypeService; + static ProcessService processService; protected Context context; @@ -127,6 +134,7 @@ public abstract class AbstractBuilder { relationshipService = ContentServiceFactory.getInstance().getRelationshipService(); relationshipTypeService = ContentServiceFactory.getInstance().getRelationshipTypeService(); entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService(); + processService = ScriptServiceFactory.getInstance().getProcessService(); // Temporarily disabled claimedTaskService = XmlWorkflowServiceFactory.getInstance().getClaimedTaskService(); @@ -162,6 +170,8 @@ public abstract class AbstractBuilder { relationshipService = null; relationshipTypeService = null; entityTypeService = null; + processService = null; + } public static void cleanupObjects() throws Exception { @@ -190,7 +200,7 @@ public abstract class AbstractBuilder { */ public abstract void cleanup() throws Exception; - public abstract T build(); + public abstract T build() throws SQLException, AuthorizeException; public abstract void delete(T dso) throws Exception; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/AbstractDSpaceObjectBuilder.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/AbstractDSpaceObjectBuilder.java index b61cb041f2..9fbd0ff0cd 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/AbstractDSpaceObjectBuilder.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/AbstractDSpaceObjectBuilder.java @@ -7,9 +7,11 @@ */ package org.dspace.app.rest.builder; +import java.sql.SQLException; import java.util.Date; import org.apache.logging.log4j.Logger; +import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.ResourcePolicy; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; @@ -65,6 +67,19 @@ public abstract class AbstractDSpaceObjectBuilder return (B) this; } + protected > B addMetadataValue(final T dso, final String schema, + final String element, + final String qualifier, + final String language, + final String value) { + try { + getService().addMetadata(context, dso, schema, element, qualifier, language, value); + } catch (Exception e) { + return handleException(e); + } + return (B) this; + } + protected > B setMetadataSingleValue(final T dso, final String schema, final String element, final String qualifier, @@ -216,7 +231,7 @@ public abstract class AbstractDSpaceObjectBuilder return (B) this; } - public abstract T build(); + public abstract T build() throws SQLException, AuthorizeException; public void delete(T dso) throws Exception { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/BitstreamBuilder.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/BitstreamBuilder.java index ae6a447b7f..3de10b723d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/BitstreamBuilder.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/BitstreamBuilder.java @@ -43,6 +43,12 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder { return builder.create(context, item, is); } + public static BitstreamBuilder createBitstream(Context context, Bundle bundle, InputStream is) + throws SQLException, AuthorizeException, IOException { + BitstreamBuilder builder = new BitstreamBuilder(context); + return builder.create(context, bundle, is); + } + private BitstreamBuilder create(Context context, Item item, InputStream is) throws SQLException, AuthorizeException, IOException { this.context = context; @@ -55,6 +61,16 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder { return this; } + private BitstreamBuilder create(Context context, Bundle bundle, InputStream is) + throws SQLException, AuthorizeException, IOException { + this.context = context; + this.item = bundle.getItems().get(0); + bitstream = bitstreamService.create(context, bundle, is); + + return this; + } + + public BitstreamBuilder withName(String name) throws SQLException { bitstream.setName(context, name); return this; diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/BundleBuilder.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/BundleBuilder.java new file mode 100644 index 0000000000..bd98e3ced9 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/BundleBuilder.java @@ -0,0 +1,70 @@ +/** + * 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.ArrayList; +import java.util.List; + +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.content.service.DSpaceObjectService; +import org.dspace.core.Context; + +public class BundleBuilder extends AbstractDSpaceObjectBuilder { + + private Bundle bundle; + private Item item; + private String name; + private List bitstreams = new ArrayList<>(); + + protected BundleBuilder(Context context) { + super(context); + } + + public static BundleBuilder createBundle(final Context context, final Item item) { + BundleBuilder builder = new BundleBuilder(context); + return builder.create(context, item); + } + + private BundleBuilder create(Context context, Item item) { + this.context = context; + this.item = item; + return this; + } + + public BundleBuilder withName(String name) { + this.name = name; + return this; + } + + public BundleBuilder withBitstream(Bitstream bitstream) { + this.bitstreams.add(bitstream); + return this; + } + + public void cleanup() throws Exception { + delete(bundle); + } + + protected DSpaceObjectService getService() { + return bundleService; + } + + public Bundle build() throws SQLException, AuthorizeException { + bundle = bundleService.create(context, item, name); + + for (Bitstream bitstream: bitstreams) { + bundleService.addBitstream(context, bundle, bitstream); + } + + return bundle; + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/CollectionBuilder.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/CollectionBuilder.java index f50487b076..ffc8f56931 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/CollectionBuilder.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/CollectionBuilder.java @@ -55,6 +55,10 @@ public class CollectionBuilder extends AbstractDSpaceObjectBuilder { return setMetadataSingleValue(collection, MetadataSchemaEnum.DC.getName(), "title", null, name); } + public CollectionBuilder withNameForLanguage(final String name, final String language) { + return addMetadataValue(collection, MetadataSchemaEnum.DC.getName(), "title", null, language, name); + } + public CollectionBuilder withLogo(final String content) throws AuthorizeException, IOException, SQLException { InputStream is = IOUtils.toInputStream(content, CharEncoding.UTF_8); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/ProcessBuilder.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/ProcessBuilder.java new file mode 100644 index 0000000000..9e200b44f2 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/ProcessBuilder.java @@ -0,0 +1,76 @@ +/** + * 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.dspace.content.ProcessStatus; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.scripts.DSpaceCommandLineParameter; +import org.dspace.scripts.Process; +import org.dspace.scripts.service.ProcessService; + +public class ProcessBuilder extends AbstractBuilder { + + private Process process; + + protected ProcessBuilder(Context context) { + super(context); + } + + public static ProcessBuilder createProcess(Context context, EPerson ePerson, String scriptName, + List parameters) + throws SQLException { + ProcessBuilder processBuilder = new ProcessBuilder(context); + return processBuilder.create(context, ePerson, scriptName, parameters); + } + + private ProcessBuilder create(Context context, EPerson ePerson, String scriptName, + List parameters) + throws SQLException { + this.context = context; + this.process = processService.create(context, ePerson, scriptName, parameters); + this.process.setProcessStatus(ProcessStatus.SCHEDULED); + return this; + } + + public void cleanup() throws Exception { + delete(process); + } + + public Process build() { + try { + processService.update(context, process); + context.dispatchEvents(); + indexingService.commit(); + } catch (Exception e) { + return null; + } + return process; + } + + public void delete(Process dso) throws Exception { + try (Context c = new Context()) { + c.turnOffAuthorisationSystem(); + Process attachedDso = c.reloadEntity(dso); + if (attachedDso != null) { + getService().delete(c, attachedDso); + } + c.complete(); + } + + indexingService.commit(); + } + + + protected ProcessService getService() { + return processService; + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/util/AbstractBuilderCleanupUtil.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/util/AbstractBuilderCleanupUtil.java index 7862d1f9d1..9570bcabac 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/util/AbstractBuilderCleanupUtil.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/util/AbstractBuilderCleanupUtil.java @@ -25,6 +25,7 @@ import org.dspace.app.rest.builder.ItemBuilder; import org.dspace.app.rest.builder.MetadataFieldBuilder; import org.dspace.app.rest.builder.MetadataSchemaBuilder; import org.dspace.app.rest.builder.PoolTaskBuilder; +import org.dspace.app.rest.builder.ProcessBuilder; import org.dspace.app.rest.builder.RelationshipBuilder; import org.dspace.app.rest.builder.RelationshipTypeBuilder; import org.dspace.app.rest.builder.SiteBuilder; @@ -61,6 +62,7 @@ public class AbstractBuilderCleanupUtil { map.put(MetadataFieldBuilder.class.getName(), new LinkedList<>()); map.put(MetadataSchemaBuilder.class.getName(), new LinkedList<>()); map.put(SiteBuilder.class.getName(), new LinkedList<>()); + map.put(ProcessBuilder.class.getName(), new LinkedList<>()); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/ConverterServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/ConverterServiceIT.java new file mode 100644 index 0000000000..50069f0366 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/ConverterServiceIT.java @@ -0,0 +1,302 @@ +/** + * 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 static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.Map; +import java.util.TreeSet; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Sets; +import org.dspace.app.rest.model.MockObject; +import org.dspace.app.rest.model.MockObjectRest; +import org.dspace.app.rest.model.RestAddressableModel; +import org.dspace.app.rest.model.RestModel; +import org.dspace.app.rest.model.hateoas.EmbeddedPage; +import org.dspace.app.rest.model.hateoas.HALResource; +import org.dspace.app.rest.model.hateoas.MockObjectResource; +import org.dspace.app.rest.projection.MockProjection; +import org.dspace.app.rest.projection.Projection; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.junit.Test; +import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.Resource; + +/** + * Tests functionality of {@link ConverterService}. + */ +public class ConverterServiceIT extends AbstractControllerIntegrationTest { + + @Autowired + private ConverterService converter; + + @Mock + private Object mockObjectWithNoConverter; + + @Mock + private RestModel mockObjectRestWithNoResource; + + @Mock + private Link mockLink; + + @Mock + private Object mockEmbeddedResource; + + /** + * When calling {@code toRest} with an object for which an appropriate {@link DSpaceConverter} can't be found, + * it should throw an {@link IllegalArgumentException}. + */ + @Test + public void toRestNoConverterFound() { + try { + converter.toRest(mockObjectWithNoConverter, Projection.DEFAULT); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), startsWith("No converter found")); + } + } + + /** + * When calling {@code toRest} and the inferred return type is incompatible with the converter's output, + * it should throw a {@link ClassCastException}. + */ + @Test(expected = ClassCastException.class) + public void toRestWrongReturnType() { + @SuppressWarnings("unused") + String restObject = converter.toRest(MockObject.create(0), Projection.DEFAULT); + } + + /** + * When calling {@code toRest} with the default projection, the converter should run and no changes should be made. + */ + @Test + public void toRestWithDefaultProjection() { + long id = 0; + MockObjectRest restObject = converter.toRest(MockObject.create(id), Projection.DEFAULT); + assertThat(restObject.getId(), equalTo(id)); + assertThat(restObject.getValue(), equalTo("value" + id)); + } + + /** + * When calling {@code toRest} with a custom projection, {@link Projection#transformModel(Object)} should + * be called before conversion, then {@link Projection#transformRest(RestModel)} should be called before + * returning. + */ + @Test + public void toRestWithMockProjection() { + long id = 0; + MockProjection mockProjection = new MockProjection(mockLink, mockEmbeddedResource); + MockObjectRest restObject = converter.toRest(MockObject.create(id), mockProjection); + assertThat(restObject.getId(), equalTo((id + 1) * 3)); + assertThat(restObject.getValue(), equalTo("value" + id + "?!")); + } + + /** + * When calling {@code toResource}, if an appropriate constructor could not be found, + * it should throw an {@link IllegalArgumentException}. + */ + @Test + public void toResourceNoConstructorFound() { + try { + converter.toResource(mockObjectRestWithNoResource); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), startsWith("No constructor found")); + } + } + + /** + * When calling {@code toResource} and the inferred return type is incompatible with the resource constructor, + * it should throw a {@link ClassCastException}. + */ + @Test(expected = ClassCastException.class) + public void toResourceWrongReturnType() { + @SuppressWarnings("unused") + MockHalResource mockHalResource = converter.toResource(MockObjectRest.create(0)); + } + + /** + * When calling {@code toResource} with the default projection, the result should have all + * the expected links and embeds with no changes introduced by the projection. + */ + @Test + public void toResourceWithDefaultProjection() throws Exception { + MockObjectRest r0 = MockObjectRest.create(0); + MockObjectRest r1 = MockObjectRest.create(1); + MockObjectRest r2 = MockObjectRest.create(2); + MockObjectRest r6 = MockObjectRest.create(6); + r0.setRestProp1(r1); + r0.setRestProp2(r2); + r0.setRestProp6(r6); + String r0json = new ObjectMapper().writeValueAsString(r0); + + MockObjectResource resource = converter.toResource(r0); + + // The default projection should not modify the wrapped object + assertThat(new ObjectMapper().writeValueAsString(r0), equalTo(r0json)); + + assertHasEmbeds(resource, new String[] { + "restProp1", // restProp1 embedded; value != null, embedOptional == false + "restProp2", // restProp2 embedded; value != null, embedOptional == true + // restProp3 not embedded; value == null, embedOptional == true + // restProp4 not embedded; value == null, embedOptional == true + "restPropFive", // restPropFive embedded; value == null, embedOptional == false + "restProp6", // restProp6 embedded; value != null, embedOptional == false + "oChildren", // oChildren embedded; value != null, embedOptional == true + "aChildren" // aChildren embedded; value != null, embedOptional == false + // nChildren not embedded; value != null, linkOptional == false, embedOptional == false + // (embed disallowed by link repository) + }, new Class[] { + Resource.class, + Resource.class, + null, + Resource.class, + EmbeddedPage.class, + EmbeddedPage.class + }); + + assertEmbeddedPageSize(resource, "oChildren", 2); + assertEmbeddedPageSize(resource, "aChildren", 2); + + assertHasLinks(resource, new String[] { + "self", // self linked; (added by DSpaceResourceHalLinkFactory) + "restProp1", // restProp1 linked; value != null, linkOptional == true, embedOptional == false + "restProp2", // restProp2 linked; value != null, linkOptional == true, embedOptional == true + // restProp3 not linked; value == null, linkOptional == true, embedOptional == true + "restProp4", // restProp4 linked; value == null, linkOptional == false, embedOptional == true + "restPropFive", // restPropFive linked; value == null, linkOptional == false, embedOptional == false + "restProp6", // restProp6 linked; value != null, linkOptional == false, embedOptional == false + "oChildren", // oChildren linked; value != null, linkOptional == true, embedOptional == true + "aChildren", // aChildren linked; value != null, linkOptional == true, embedOptional == false + "nChildren" // nChildren linked; value != null, linkOptional == false, embedOptional == false + }); + } + + /** + * When calling {@code toResource} with a custom projection, the result should have all + * the expected links and embeds, including/excluding any changes introduced by the projection. + */ + @Test + public void toResourceWithMockProjection() throws Exception { + MockObjectRest r0 = MockObjectRest.create(0); + MockObjectRest r1 = MockObjectRest.create(1); + MockObjectRest r2 = MockObjectRest.create(2); + MockObjectRest r6 = MockObjectRest.create(6); + r0.setRestProp1(r1); + r0.setRestProp2(r2); + r0.setRestProp6(r6); + String r0json = new ObjectMapper().writeValueAsString(r0); + + when(mockLink.getRel()).thenReturn("mockLink"); + r0.setProjection(new MockProjection(mockLink, mockEmbeddedResource)); + + MockObjectResource resource = converter.toResource(r0); + + // The mock projection should not modify the wrapped object + assertThat(new ObjectMapper().writeValueAsString(r0), equalTo(r0json)); + + assertHasEmbeds(resource, new String[] { + "restProp1", // restProp1 embedded; value != null, embedOptional == false + // restProp2 not embedded; value != null, embedOptional == true + // restProp3 not embedded; value == null, embedOptional == true + // restProp4 not embedded; value == null, embedOptional == true + "restPropFive", // restPropFive embedded; value == null, embedOptional == false + "restProp6", // restProp6 embedded; value != null, embedOptional == false + // oChildren not embedded; value != null, embedOptional == true + "aChildren", // aChildren embedded; value != null, embedOptional == false + // nChildren not embedded; value != null, linkOptional == false, embedOptional == false + // (embed disallowed by link repository) + "resource" // resource embedded (added by MockProjection) + }, new Class[] { + Resource.class, + null, + Resource.class, + EmbeddedPage.class, + Object.class + }); + + assertEmbeddedPageSize(resource, "aChildren", 2); + + assertHasLinks(resource, new String[] { + "self", // self linked; (added by DSpaceResourceHalLinkFactory) + "restProp1", // restProp1 linked; value != null, linkOptional == true, embedOptional == false + // restProp2 not linked; value != null, linkOptional == true, embedOptional == true + // restProp3 not linked; value == null, linkOptional == true, embedOptional == true + "restProp4", // restProp4 linked; value == null, linkOptional == false, embedOptional == true + "restPropFive", // restPropFive linked; value == null, linkOptional == false, embedOptional == false + "restProp6", // restProp6 linked; value != null, linkOptional == false, embedOptional == false + // oChildren not linked; value != null, linkOptional == true, embedOptional == true + "aChildren", // aChildren linked; value != null, linkOptional == true, embedOptional == false + "nChildren", // nChildren linked; value != null, linkOptional == false, embedOptional == false + "mockLink" // mockLink linked; (added by MockProjection) + }); + } + + private void assertHasLinks(Resource resource, String[] rels) { + Map map = new HashMap<>(); + resource.getLinks().stream().forEach((link) -> map.put(link.getRel(), link)); + assertThat(new TreeSet(map.keySet()), equalTo(new TreeSet(Sets.newHashSet(rels)))); + } + + private void assertHasEmbeds(HALResource resource, String[] rels, Class[] classes) { + assertThat(new TreeSet(resource.getEmbeddedResources().keySet()), equalTo(new TreeSet(Sets.newHashSet(rels)))); + for (int i = 0; i < rels.length; i++) { + String rel = rels[i]; + Class expectedClass = classes[i]; + Object value = resource.getEmbeddedResources().get(rel); + if (expectedClass == null) { + if (value != null) { + fail("expected null value for embed: " + rel); + } + } else if (value == null) { + fail("got null value, but expected a " + expectedClass + " for embed: " + rel); + } else { + assertTrue("got a " + value.getClass() + " value, but expected a " + + expectedClass + " for embed: " + rel, expectedClass.isAssignableFrom(value.getClass())); + } + } + } + + private void assertEmbeddedPageSize(HALResource resource, String rel, int expectedSize) { + assertEquals(expectedSize, ((EmbeddedPage) resource.getEmbeddedResources() + .get(rel)).getPageContent().get(rel).size()); + } + + class MockRestAddressableModel extends RestAddressableModel { + @Override + public String getCategory() { + return null; + } + + @Override + public Class getController() { + return null; + } + + @Override + public String getType() { + return null; + } + } + + class MockHalResource extends HALResource { + public MockHalResource(MockRestAddressableModel content) { + super(content); + } + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/DiscoverConfigurationConverterTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/DiscoverConfigurationConverterTest.java index 5cd2b72107..333d624010 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/DiscoverConfigurationConverterTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/DiscoverConfigurationConverterTest.java @@ -16,6 +16,7 @@ import static org.mockito.Mockito.when; import java.util.LinkedList; import org.dspace.app.rest.model.SearchConfigurationRest; +import org.dspace.app.rest.projection.Projection; import org.dspace.discovery.configuration.DiscoveryConfiguration; import org.dspace.discovery.configuration.DiscoverySearchFilter; import org.dspace.discovery.configuration.DiscoverySortConfiguration; @@ -56,20 +57,20 @@ public class DiscoverConfigurationConverterTest { @Test public void testReturnType() throws Exception { populateDiscoveryConfigurationWithEmptyList(); - searchConfigurationRest = discoverConfigurationConverter.convert(discoveryConfiguration); + searchConfigurationRest = discoverConfigurationConverter.convert(discoveryConfiguration, Projection.DEFAULT); assertTrue(searchConfigurationRest.getFilters().isEmpty()); assertEquals(SearchConfigurationRest.class, searchConfigurationRest.getClass()); } @Test public void testConvertWithNullParamter() throws Exception { - assertNotNull(discoverConfigurationConverter.convert(null)); + assertNotNull(discoverConfigurationConverter.convert(null, Projection.DEFAULT)); } @Test public void testNoSearchSortConfigurationReturnObjectNotNull() throws Exception { discoveryConfiguration.setSearchFilters(new LinkedList<>()); - searchConfigurationRest = discoverConfigurationConverter.convert(discoveryConfiguration); + searchConfigurationRest = discoverConfigurationConverter.convert(discoveryConfiguration, Projection.DEFAULT); assertTrue(discoveryConfiguration.getSearchFilters().isEmpty()); assertTrue(searchConfigurationRest.getFilters().isEmpty()); assertNotNull(searchConfigurationRest); @@ -78,7 +79,7 @@ public class DiscoverConfigurationConverterTest { @Test public void testNoSearchFilterReturnObjectNotNull() throws Exception { discoveryConfiguration.setSearchSortConfiguration(new DiscoverySortConfiguration()); - searchConfigurationRest = discoverConfigurationConverter.convert(discoveryConfiguration); + searchConfigurationRest = discoverConfigurationConverter.convert(discoveryConfiguration, Projection.DEFAULT); assertTrue(searchConfigurationRest.getFilters().isEmpty()); assertNotNull(searchConfigurationRest); } @@ -87,7 +88,7 @@ public class DiscoverConfigurationConverterTest { // are null @Test public void testNoSearchSortConfigurationAndNoSearchFilterReturnObjectNotNull() throws Exception { - searchConfigurationRest = discoverConfigurationConverter.convert(discoveryConfiguration); + searchConfigurationRest = discoverConfigurationConverter.convert(discoveryConfiguration, Projection.DEFAULT); assertNotNull(searchConfigurationRest); } @@ -108,7 +109,7 @@ public class DiscoverConfigurationConverterTest { when(discoveryConfiguration.getSearchSortConfiguration()).thenReturn(discoverySortConfiguration); when(discoverySortConfiguration.getSortFields()).thenReturn(mockedList); - searchConfigurationRest = discoverConfigurationConverter.convert(discoveryConfiguration); + searchConfigurationRest = discoverConfigurationConverter.convert(discoveryConfiguration, Projection.DEFAULT); int counter = 0; for (SearchConfigurationRest.SortOption sortOption : searchConfigurationRest.getSortOptions()) { @@ -123,7 +124,7 @@ public class DiscoverConfigurationConverterTest { @Test public void testEmptySortOptionsAfterConvertWithConfigurationWithEmptySortFields() throws Exception { populateDiscoveryConfigurationWithEmptyList(); - searchConfigurationRest = discoverConfigurationConverter.convert(discoveryConfiguration); + searchConfigurationRest = discoverConfigurationConverter.convert(discoveryConfiguration, Projection.DEFAULT); assertEquals(0, searchConfigurationRest.getSortOptions().size()); } @@ -132,7 +133,7 @@ public class DiscoverConfigurationConverterTest { public void testEmptySortOptionsAfterConvertWithConfigurationWithNullSortFields() throws Exception { populateDiscoveryConfigurationWithEmptyList(); when(discoveryConfiguration.getSearchSortConfiguration()).thenReturn(null); - searchConfigurationRest = discoverConfigurationConverter.convert(discoveryConfiguration); + searchConfigurationRest = discoverConfigurationConverter.convert(discoveryConfiguration, Projection.DEFAULT); assertEquals(0, searchConfigurationRest.getSortOptions().size()); } @@ -151,7 +152,7 @@ public class DiscoverConfigurationConverterTest { mockedList.add(discoverySearchFilter1); when(discoveryConfiguration.getSearchFilters()).thenReturn(mockedList); - searchConfigurationRest = discoverConfigurationConverter.convert(discoveryConfiguration); + searchConfigurationRest = discoverConfigurationConverter.convert(discoveryConfiguration, Projection.DEFAULT); int counter = 0; for (SearchConfigurationRest.Filter filter : searchConfigurationRest.getFilters()) { @@ -169,7 +170,7 @@ public class DiscoverConfigurationConverterTest { @Test public void testEmptySearchFilterAfterConvertWithConfigurationWithEmptySearchFilters() throws Exception { populateDiscoveryConfigurationWithEmptyList(); - searchConfigurationRest = discoverConfigurationConverter.convert(discoveryConfiguration); + searchConfigurationRest = discoverConfigurationConverter.convert(discoveryConfiguration, Projection.DEFAULT); assertEquals(0, searchConfigurationRest.getFilters().size()); } @@ -178,7 +179,7 @@ public class DiscoverConfigurationConverterTest { populateDiscoveryConfigurationWithEmptyList(); when(discoveryConfiguration.getSearchFilters()).thenReturn(null); - searchConfigurationRest = discoverConfigurationConverter.convert(discoveryConfiguration); + searchConfigurationRest = discoverConfigurationConverter.convert(discoveryConfiguration, Projection.DEFAULT); assertEquals(0, searchConfigurationRest.getFilters().size()); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/MockObjectConverter.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/MockObjectConverter.java new file mode 100644 index 0000000000..d8e12602ae --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/MockObjectConverter.java @@ -0,0 +1,34 @@ +/** + * 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.MockObject; +import org.dspace.app.rest.model.MockObjectRest; +import org.dspace.app.rest.projection.Projection; +import org.springframework.stereotype.Component; + +/** + * A simple {@link DSpaceConverter} for use with tests. + */ +@Component +public class MockObjectConverter implements DSpaceConverter { + + @Override + public MockObjectRest convert(MockObject modelObject, Projection projection) { + MockObjectRest restObject = new MockObjectRest(); + restObject.setProjection(projection); + restObject.setId(modelObject.getStoredId()); + restObject.setValue(modelObject.getStoredValue()); + return restObject; + } + + @Override + public Class getModelClass() { + return MockObject.class; + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CSVMetadataImportReferenceTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CSVMetadataImportReferenceTest.java index 65d6d9f119..57a0475b63 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CSVMetadataImportReferenceTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CSVMetadataImportReferenceTest.java @@ -86,9 +86,9 @@ public class CSVMetadataImportReferenceTest extends AbstractEntityIntegrationTes } } if (placeDirection.equalsIgnoreCase("left")) { - assertEquals(relationship.getLeftPlace(), placeCount); + assertEquals(placeCount, relationship.getLeftPlace()); } else { - assertEquals(relationship.getRightPlace(), placeCount); + assertEquals(placeCount, relationship.getRightPlace()); } assertEquals(expectedCount, foundCount); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BitstreamMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BitstreamMatcher.java index 9ff71928e5..9e4175c3c6 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BitstreamMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BitstreamMatcher.java @@ -42,6 +42,24 @@ public class BitstreamMatcher { ); } + public static Matcher matchBitstreamEntry(UUID uuid, long size, String name, String description) { + return allOf( + //Check ID and size + hasJsonPath("$.uuid", is(uuid.toString())), + hasJsonPath("$.sizeBytes", is((int) size)), + hasJsonPath("$.metadata", allOf( + matchMetadata("dc.title", name), + matchMetadata("dc.description", description) + )), + //Make sure we have a checksum + hasJsonPath("$.checkSum", matchChecksum()), + //Make sure we have a valid format + hasJsonPath("$._embedded.format", matchFormat()), + //Check links + matchBitstreamLinks(uuid) + ); + } + public static Matcher matchBitstreamEntry(UUID uuid, long size) { return allOf( //Check ID and size diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BundleMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BundleMatcher.java new file mode 100644 index 0000000000..7e34ae73bc --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BundleMatcher.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.app.rest.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.is; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.dspace.content.Bitstream; +import org.dspace.core.Constants; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; + +public class BundleMatcher { + + private BundleMatcher() { + } + + + public static Matcher matchBundle(String name, + UUID uuid, + String handle, + int type, + List bitstreams) { + return allOf( + hasJsonPath("$.uuid", is(uuid.toString())), + hasJsonPath("$.name", is(name)), + hasJsonPath("$.handle", is(handle)), + hasJsonPath("$.type", is(Constants.typeText[type].toLowerCase())), + hasJsonPath("$.metadata", Matchers.allOf( + MetadataMatcher.matchMetadata("dc.title", name) + )), + hasJsonPath("$._embedded.bitstreams._embedded.bitstreams", Matchers.containsInAnyOrder( + bitstreams + .stream() + .map(x -> BitstreamMatcher.matchBitstreamEntry(x.getID(), x.getSizeBytes())) + .collect(Collectors.toList()) + )), + matchLinks(uuid) + ); + } + + private static Matcher matchLinks(UUID uuid) { + return allOf( + hasJsonPath("$._links.primaryBitstream.href", endsWith("/api/core/bundles/" + uuid + "/primaryBitstream")), + hasJsonPath("$._links.bitstreams.href", endsWith("/api/core/bundles/" + uuid + "/bitstreams")), + hasJsonPath("$._links.self.href", endsWith("/api/core/bundles/" + uuid)) + ); + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java index 0f6eb553f4..e78550df54 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java @@ -46,7 +46,7 @@ public class ItemMatcher { public static Matcher matchItemLinks(Item item) { return allOf( hasJsonPath("$._links.self.href", startsWith(REST_SERVER_URL)), - hasJsonPath("$._links.bitstreams.href", startsWith(REST_SERVER_URL)), + hasJsonPath("$._links.bundles.href", startsWith(REST_SERVER_URL)), hasJsonPath("$._links.owningCollection.href", startsWith(REST_SERVER_URL)), hasJsonPath("$._links.templateItemOf.href", startsWith(REST_SERVER_URL)) ); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ParameterMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ParameterMatcher.java new file mode 100644 index 0000000000..cf6c516971 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ParameterMatcher.java @@ -0,0 +1,29 @@ +/** + * 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 org.apache.commons.cli.Option; +import org.hamcrest.Matcher; + +public class ParameterMatcher { + + private ParameterMatcher() { + } + + public static Matcher matchParameter(Option option) { + return allOf( + hasJsonPath("$.name", is("-" + option.getOpt())), + hasJsonPath("$.description", is(option.getDescription())), + hasJsonPath("$.type", is(((Class) option.getType()).getSimpleName())) + ); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ParameterValueMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ParameterValueMatcher.java new file mode 100644 index 0000000000..d4be90f8c6 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ParameterValueMatcher.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.app.rest.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.is; + +import org.hamcrest.Matcher; + +public class ParameterValueMatcher { + + private ParameterValueMatcher() {} + + public static Matcher matchParameterValue(String name, String value) { + return allOf( + hasJsonPath("$.name", is(name)), + hasJsonPath("$.value", is(value)) + ); + + } + public static Matcher matchParameterValue(String name) { + return allOf( + hasJsonPath("$.name", is(name)) + ); + + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ProcessMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ProcessMatcher.java new file mode 100644 index 0000000000..2ac00eb4ab --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ProcessMatcher.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.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.any; +import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.dspace.content.ProcessStatus; +import org.dspace.scripts.DSpaceCommandLineParameter; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; + +public class ProcessMatcher { + + private ProcessMatcher() { + } + + private static Matcher matchProcess(String name, String userId) { + return allOf( + hasJsonPath("$.scriptName", is(name)), + hasJsonPath("$.userId", is(userId)) + ); + + } + + public static Matcher matchProcess(String name, String userId, Integer processId, + List list, ProcessStatus status) { + return allOf( + matchProcess(name, userId, list, Collections.singletonList(status)), + hasJsonPath("$.processId", is(processId)) + ); + } + + public static Matcher matchProcess(String name, String userId, + List list, ProcessStatus status) { + return allOf( + matchProcess(name, userId, list, Collections.singletonList(status)) + ); + } + + public static Matcher matchProcess(String name, String userId, + List list, + List statuses) { + return allOf( + matchProcess(name, userId), + hasJsonPath("$.startTime", anyOf(any(String.class), nullValue())), + hasJsonPath("$.endTime", anyOf(any(String.class), nullValue())), + hasJsonPath("$.processStatus", Matchers.anyOf( + statuses.stream().map(status -> is(status.toString())).collect(Collectors.toList()) + )), + hasJsonPath("$.parameters", Matchers.containsInAnyOrder( + list.stream().map(dSpaceCommandLineParameter -> ParameterValueMatcher + .matchParameterValue(dSpaceCommandLineParameter.getName(), dSpaceCommandLineParameter.getValue())) + .collect(Collectors.toList()) + )) + ); + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ScriptMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ScriptMatcher.java new file mode 100644 index 0000000000..d348ee76c4 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ScriptMatcher.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.app.rest.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.is; + +import org.apache.commons.cli.Options; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; + +public class ScriptMatcher { + + private ScriptMatcher() { + } + + public static Matcher matchScript(String name, String description) { + return allOf( + hasJsonPath("$.name", is(name)), + hasJsonPath("$.description", is(description)) + ); + } + + public static Matcher matchMockScript(Options options) { + return allOf( + matchScript("mock-script", "Mocking a script for testing purposes"), + hasJsonPath("$.parameters", Matchers.containsInAnyOrder( + ParameterMatcher.matchParameter(options.getOption("r")), + ParameterMatcher.matchParameter(options.getOption("i")) + )) + ); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubmissionFormFieldMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubmissionFormFieldMatcher.java index 724957ff3f..67f2494cf3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubmissionFormFieldMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubmissionFormFieldMatcher.java @@ -47,7 +47,8 @@ public class SubmissionFormFieldMatcher { * @return a Matcher for all the condition above */ public static Matcher matchFormFieldDefinition(String type, String label, String mandatoryMessage, - boolean repeatable, String hints, String metadata) { + boolean repeatable, + String hints, String metadata) { return matchFormFieldDefinition(type, label, mandatoryMessage, repeatable, hints, null, metadata); } @@ -73,8 +74,8 @@ public class SubmissionFormFieldMatcher { * @return a Matcher for all the condition above */ public static Matcher matchFormFieldDefinition(String type, String label, String mandatoryMessage, - boolean repeatable, String hints, String style, - String metadata) { + boolean repeatable, + String hints, String style, String metadata) { return allOf( // check each field definition hasJsonPath("$.input.type", is(type)), @@ -113,19 +114,24 @@ public class SubmissionFormFieldMatcher { * the optional filter to be used for the lookup * @param searchConfiguration * the searchConfiguration to be used for the lookup + * @param nameVariants + * the optional name variants allowed flag * @return a Matcher for all the condition above */ public static Matcher matchFormOpenRelationshipFieldDefinition(String type, String label, String mandatoryMessage, - boolean repeatable, String hints, + boolean repeatable, + String hints, String metadata, String relationshipType, String filter, - String searchConfiguration) { + String searchConfiguration, + boolean nameVariants) { return allOf( hasJsonPath("$.selectableRelationship.relationshipType", is(relationshipType)), hasJsonPath("$.selectableRelationship.filter", is(filter)), hasJsonPath("$.selectableRelationship.searchConfiguration", is(searchConfiguration)), + hasJsonPath("$.selectableRelationship.nameVariants", is(String.valueOf(nameVariants))), matchFormFieldDefinition(type, label, mandatoryMessage, repeatable, hints, metadata)); } @@ -148,18 +154,23 @@ public class SubmissionFormFieldMatcher { * the optional filter to be used for the lookup * @param searchConfiguration * the searchConfiguration to be used for the lookup + * @param nameVariants + * the optional name variants allowed flag * @return a Matcher for all the condition above */ public static Matcher matchFormClosedRelationshipFieldDefinition(String label, String mandatoryMessage, - boolean repeatable, String hints, + boolean repeatable, + String hints, String relationshipType, String filter, - String searchConfiguration) { + String searchConfiguration, + boolean nameVariants) { return allOf( hasJsonPath("$.selectableRelationship.relationshipType", is(relationshipType)), hasJsonPath("$.selectableRelationship.filter", is(filter)), hasJsonPath("$.selectableRelationship.searchConfiguration", is(searchConfiguration)), + hasJsonPath("$.selectableRelationship.nameVariants", is(String.valueOf(nameVariants))), hasJsonPath("$.label", is(label)), mandatoryMessage != null ? hasJsonPath("$.mandatoryMessage", containsString(mandatoryMessage)) : hasNoJsonPath("$.mandatoryMessage"), diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/model/MockObject.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/model/MockObject.java new file mode 100644 index 0000000000..5268b0b253 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/model/MockObject.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; + +/** + * A simple model object for use with tests. + * + * Simulates a typical JPA object obtained through the DSpace service layer. + */ +public class MockObject { + + private Long storedId; + + private String storedValue; + + public static MockObject create(long id) { + MockObject mockObject = new MockObject(); + mockObject.setStoredId(id); + mockObject.setStoredValue("value" + id); + return mockObject; + } + + public Long getStoredId() { + return storedId; + } + + public void setStoredId(Long storedId) { + this.storedId = storedId; + } + + public String getStoredValue() { + return storedValue; + } + + public void setStoredValue(String storedValue) { + this.storedValue = storedValue; + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/model/MockObjectRest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/model/MockObjectRest.java new file mode 100644 index 0000000000..f335177993 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/model/MockObjectRest.java @@ -0,0 +1,145 @@ +/** + * 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; +import org.dspace.app.rest.projection.Projection; + +/** + * A simple rest object for use with tests. + */ +@LinksRest(links = { + @LinkRest( + name = MockObjectRest.O_CHILDREN, + linkClass = MockObjectRest.class, + method = "getMockObjectChildren", + embedOptional = true, + linkOptional = true + ), + @LinkRest( + name = MockObjectRest.A_CHILDREN, + linkClass = MockObjectRest.class, + method = "getMockObjectChildren", + linkOptional = true + ), + @LinkRest( + name = MockObjectRest.N_CHILDREN, + linkClass = MockObjectRest.class, + method = "getMockObjectChildren" + ) +}) +public class MockObjectRest extends BaseObjectRest { + + public static final String CATEGORY = "test"; + + public static final String NAME = "testobject"; + + public static final String O_CHILDREN = "oChildren"; + + public static final String A_CHILDREN = "aChildren"; + + public static final String N_CHILDREN = "nChildren"; + + private String value; + + private MockObjectRest restProp1; + + private MockObjectRest restProp2; + + private MockObjectRest restProp3; + + private MockObjectRest restProp4; + + private MockObjectRest restProp5; + + private MockObjectRest restProp6; + + public static MockObjectRest create(long id) { + MockObjectRest mockObjectRest = new MockObjectRest(); + mockObjectRest.setProjection(Projection.DEFAULT); + mockObjectRest.setId(id); + mockObjectRest.setValue("value" + id); + return mockObjectRest; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return RestResourceController.class; + } + + @Override + public String getType() { + return NAME; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @LinkRest(linkClass = MockObjectRest.class, linkOptional = true) + public MockObjectRest getRestProp1() { + return restProp1; + } + + public void setRestProp1(MockObjectRest restProp1) { + this.restProp1 = restProp1; + } + + @LinkRest(linkClass = MockObjectRest.class, embedOptional = true, linkOptional = true) + public MockObjectRest getRestProp2() { + return restProp2; + } + + public void setRestProp2(MockObjectRest restProp2) { + this.restProp2 = restProp2; + } + + @LinkRest(linkClass = MockObjectRest.class, embedOptional = true, linkOptional = true) + public MockObjectRest getRestProp3() { + return restProp3; + } + + public void setRestProp3(MockObjectRest restProp3) { + this.restProp3 = restProp3; + } + + @LinkRest(linkClass = MockObjectRest.class, embedOptional = true) + public MockObjectRest getRestProp4() { + return restProp4; + } + + public void setRestProp4(MockObjectRest restProp4) { + this.restProp4 = restProp4; + } + + @LinkRest(linkClass = MockObjectRest.class, name = "restPropFive") + public MockObjectRest getRestProp5() { + return restProp5; + } + + public void setRestProp5(MockObjectRest restProp5) { + this.restProp5 = restProp5; + } + + public MockObjectRest getRestProp6() { + return restProp6; + } + + public void setRestProp6(MockObjectRest restProp6) { + this.restProp6 = restProp6; + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/model/hateoas/MockObjectResource.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/model/hateoas/MockObjectResource.java new file mode 100644 index 0000000000..00733b65bf --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/model/hateoas/MockObjectResource.java @@ -0,0 +1,18 @@ +/** + * 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.MockObjectRest; +import org.dspace.app.rest.utils.Utils; + +public class MockObjectResource extends DSpaceResource { + + public MockObjectResource(MockObjectRest data, Utils utils) { + super(data, utils); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/projection/MockProjection.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/projection/MockProjection.java new file mode 100644 index 0000000000..b11350efa4 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/projection/MockProjection.java @@ -0,0 +1,101 @@ +/** + * 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.projection; + +import javax.annotation.Nullable; + +import org.dspace.app.rest.model.LinkRest; +import org.dspace.app.rest.model.MockObject; +import org.dspace.app.rest.model.MockObjectRest; +import org.dspace.app.rest.model.RestModel; +import org.dspace.app.rest.model.hateoas.HALResource; +import org.springframework.hateoas.Link; + +/** + * A projection for use in tests. + */ +public class MockProjection implements Projection { + + public static final String NAME = "mock"; + + private final Link linkToAdd; + + private final Object resourceToEmbed; + + public MockProjection(@Nullable Link linkToAdd, @Nullable Object resourceToEmbed) { + this.linkToAdd = linkToAdd; + this.resourceToEmbed = resourceToEmbed; + } + + public String getName() { + return NAME; + } + + /** + * When given a {@link MockObject}, adds one to the id and appends "?" to the value, if not null. + * Otherwise, returns the original object. + */ + @Override + public T transformModel(T modelObject) { + if (modelObject instanceof MockObject) { + MockObject mockObject = (MockObject) modelObject; + if (mockObject.getStoredId() != null) { + mockObject.setStoredId(mockObject.getStoredId() + 1); + } + if (mockObject.getStoredValue() != null) { + mockObject.setStoredValue(mockObject.getStoredValue() + "?"); + } + } + return modelObject; + } + + /** + * When given a {@link MockObjectRest}, multiplies the id by 3 and appends "!" to the value, if not null. + * Otherwise, returns the original object. + */ + @Override + public T transformRest(T restObject) { + if (restObject instanceof MockObjectRest) { + MockObjectRest mockObjectRest = (MockObjectRest) restObject; + if (mockObjectRest.getId() != null) { + mockObjectRest.setId(mockObjectRest.getId() * 3); + } + if (mockObjectRest.getValue() != null) { + mockObjectRest.setValue(mockObjectRest.getValue() + "!"); + } + } + return restObject; + } + + /** + * Adds link: {@code linkToAdd} if given as non-null in the constructor and adds "resource" embed: + * {@code resourceToEmbed} if given as non-null in the constructor. + */ + @Override + public T transformResource(T halResource) { + if (linkToAdd != null) { + halResource.add(linkToAdd); + } + if (resourceToEmbed != null) { + halResource.embedResource("resource", resourceToEmbed); + } + return halResource; + } + + /** Disallows all optional embeds. */ + @Override + public boolean allowOptionalEmbed(HALResource halResource, LinkRest linkRest) { + return false; + } + + /** Disallows all optional links. */ + @Override + public boolean allowOptionalLink(HALResource halResource, LinkRest linkRest) { + return false; + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/AbstractMockObjectChildLinkRepository.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/AbstractMockObjectChildLinkRepository.java new file mode 100644 index 0000000000..8d7e174057 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/AbstractMockObjectChildLinkRepository.java @@ -0,0 +1,40 @@ +/** + * 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.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.MockObject; +import org.dspace.app.rest.model.MockObjectRest; +import org.dspace.app.rest.projection.Projection; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +/** + * Abstract link repository for use with tests. + */ +abstract class AbstractMockObjectChildLinkRepository + extends AbstractDSpaceRestRepository implements LinkRestRepository { + + public Page getMockObjectChildren(@Nullable HttpServletRequest request, + Long itemId, + @Nullable Pageable optionalPageable, + Projection projection) { + List children = new ArrayList<>(); + if (itemId == 0) { + children.add(MockObject.create(101)); + children.add(MockObject.create(102)); + } + Pageable pageable = optionalPageable != null ? optionalPageable : new PageRequest(0, 20); + return converter.toRestPage(children, pageable, children.size(), projection); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectAlwaysEmbedChildLinkRepository.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectAlwaysEmbedChildLinkRepository.java new file mode 100644 index 0000000000..1b209c0e54 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectAlwaysEmbedChildLinkRepository.java @@ -0,0 +1,18 @@ +/** + * 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 org.dspace.app.rest.model.MockObjectRest; +import org.springframework.stereotype.Component; + +/** + * Link repository used by {@link MockObjectRest} to test that always-embedded subresources work correctly. + */ +@Component(MockObjectRest.CATEGORY + "." + MockObjectRest.NAME + "." + MockObjectRest.A_CHILDREN) +public class MockObjectAlwaysEmbedChildLinkRepository extends AbstractMockObjectChildLinkRepository { +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectNeverEmbedChildLinkRepository.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectNeverEmbedChildLinkRepository.java new file mode 100644 index 0000000000..7d82938ae8 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectNeverEmbedChildLinkRepository.java @@ -0,0 +1,22 @@ +/** + * 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 org.dspace.app.rest.model.MockObjectRest; +import org.springframework.stereotype.Component; + +/** + * Link repository used by {@link MockObjectRest} to test that never-embedded subresources work correctly. + */ +@Component(MockObjectRest.CATEGORY + "." + MockObjectRest.NAME + "." + MockObjectRest.N_CHILDREN) +public class MockObjectNeverEmbedChildLinkRepository extends AbstractMockObjectChildLinkRepository { + @Override + public boolean isEmbeddableRelation(Object data, String name) { + return false; + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectOptionallyEmbedChildLinkRepository.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectOptionallyEmbedChildLinkRepository.java new file mode 100644 index 0000000000..e75351a78e --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectOptionallyEmbedChildLinkRepository.java @@ -0,0 +1,18 @@ +/** + * 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 org.dspace.app.rest.model.MockObjectRest; +import org.springframework.stereotype.Component; + +/** + * Link repository used by {@link MockObjectRest} to test that optionally-embedded subresources work correctly. + */ +@Component(MockObjectRest.CATEGORY + "." + MockObjectRest.NAME + "." + MockObjectRest.O_CHILDREN) +public class MockObjectOptionallyEmbedChildLinkRepository extends AbstractMockObjectChildLinkRepository { +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractIntegrationTestWithDatabase.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractIntegrationTestWithDatabase.java index af5134e868..02bbbadf36 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractIntegrationTestWithDatabase.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/test/AbstractIntegrationTestWithDatabase.java @@ -15,6 +15,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.launcher.ScriptLauncher; import org.dspace.app.rest.builder.AbstractBuilder; +import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Community; import org.dspace.core.Context; @@ -41,7 +42,7 @@ public class AbstractIntegrationTestWithDatabase extends AbstractDSpaceIntegrati * log4j category */ private static final Logger log = LogManager - .getLogger(AbstractIntegrationTestWithDatabase.class); + .getLogger(AbstractIntegrationTestWithDatabase.class); /** * Context mock object to use in the tests. @@ -179,8 +180,9 @@ public class AbstractIntegrationTestWithDatabase extends AbstractDSpaceIntegrati // Clear the search core. MockSolrServiceImpl searchService = DSpaceServicesFactory.getInstance() - .getServiceManager() - .getServiceByName(SearchService.class.getName(), MockSolrServiceImpl.class); + .getServiceManager() + .getServiceByName(SearchService.class.getName(), + MockSolrServiceImpl.class); searchService.reset(); // Reload our ConfigurationService (to reset configs to defaults again) @@ -227,11 +229,17 @@ public class AbstractIntegrationTestWithDatabase extends AbstractDSpaceIntegrati } // Look up command in the configuration, and execute. - return ScriptLauncher.runOneCommand(commandConfigs, args, kernelImpl); + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + int status = ScriptLauncher.handleScript(args, commandConfigs, testDSpaceRunnableHandler, kernelImpl); + if (testDSpaceRunnableHandler.getException() != null) { + throw testDSpaceRunnableHandler.getException(); + } else { + return status; + } } finally { if (!context.isValid()) { setUp(); } } } -} +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java b/dspace-server-webapp/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java new file mode 100644 index 0000000000..74e2a56668 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java @@ -0,0 +1,36 @@ +/** + * 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.scripts.handler.impl; + +import org.dspace.scripts.handler.impl.CommandLineDSpaceRunnableHandler; + +/** + * This class will be used as a DSpaceRunnableHandler for the Tests so that we can stop the handler + * from calling System.exit() when a script would throw an exception + */ +public class TestDSpaceRunnableHandler extends CommandLineDSpaceRunnableHandler { + + private Exception exception = null; + + /** + * We're overriding this method so that we can stop the script from doing the System.exit() if + * an exception within the script is thrown + */ + @Override + public void handleException(String message, Exception e) { + exception = e; + } + + /** + * Generic getter for the exception + * @return the exception value of this TestDSpaceRunnableHandler + */ + public Exception getException() { + return exception; + } +} \ No newline at end of file diff --git a/dspace-server-webapp/src/test/java/org/dspace/scripts/impl/MockDSpaceRunnableScript.java b/dspace-server-webapp/src/test/java/org/dspace/scripts/impl/MockDSpaceRunnableScript.java new file mode 100644 index 0000000000..5df5c8992b --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/scripts/impl/MockDSpaceRunnableScript.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.scripts.impl; + +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.dspace.scripts.DSpaceRunnable; + +public class MockDSpaceRunnableScript extends DSpaceRunnable { + + private MockDSpaceRunnableScript() { + Options options = constructOptions(); + this.options = options; + } + + @Override + public void internalRun() throws Exception { + } + + @Override + public void setup() throws ParseException { + if (!commandLine.hasOption("i")) { + throw new ParseException("-i is a mandatory parameter"); + } + } + + private Options constructOptions() { + Options options = new Options(); + + options.addOption("r", "remove", true, "description r"); + options.getOption("r").setType(String.class); + options.addOption("i", "index", false, "description i"); + options.getOption("i").setType(boolean.class); + options.getOption("i").setRequired(true); + return options; + } +} diff --git a/dspace/bin/.gitattributes b/dspace/bin/.gitattributes new file mode 100644 index 0000000000..2deb753165 --- /dev/null +++ b/dspace/bin/.gitattributes @@ -0,0 +1,9 @@ +# Ensure Unix files in this folder always keep Unix line endings +dspace text eol=lf +dspace-info.pl text eol=lf +log-reporter text eol=lf +make-handle-config text eol=lf +start-handle-server text eol=lf + +# Ensure Windows files in this folder always keep Windows line endings +*.bat text eol=crlf \ No newline at end of file diff --git a/dspace/config/hibernate.cfg.xml b/dspace/config/hibernate.cfg.xml index aebf8d13ca..8b8d43a340 100644 --- a/dspace/config/hibernate.cfg.xml +++ b/dspace/config/hibernate.cfg.xml @@ -48,6 +48,8 @@ + + diff --git a/dspace/config/launcher.xml b/dspace/config/launcher.xml index 4c2afcdca4..3c4f205c2a 100644 --- a/dspace/config/launcher.xml +++ b/dspace/config/launcher.xml @@ -138,13 +138,6 @@ org.dspace.authority.indexer.AuthorityIndexClient - - index-discovery - Update Discovery Solr Search Index - - org.dspace.discovery.IndexClient - - itemupdate Item update tool for altering metadata and bitstream content in items diff --git a/dspace/config/spring/api/core-dao-services.xml b/dspace/config/spring/api/core-dao-services.xml index bed3009f07..b1a1f48475 100644 --- a/dspace/config/spring/api/core-dao-services.xml +++ b/dspace/config/spring/api/core-dao-services.xml @@ -32,6 +32,8 @@ + + diff --git a/dspace/config/spring/api/core-factory-services.xml b/dspace/config/spring/api/core-factory-services.xml index 9b59a5231d..a4d7ca08c0 100644 --- a/dspace/config/spring/api/core-factory-services.xml +++ b/dspace/config/spring/api/core-factory-services.xml @@ -22,6 +22,7 @@ + diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index 4a1acc2168..e7e28884c2 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -56,6 +56,9 @@ + + + @@ -113,5 +116,7 @@ + + diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml new file mode 100644 index 0000000000..8284edc05a --- /dev/null +++ b/dspace/config/spring/api/scripts.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/dspace/config/submission-forms.dtd b/dspace/config/submission-forms.dtd index 6533dcfc47..6f277ce208 100644 --- a/dspace/config/submission-forms.dtd +++ b/dspace/config/submission-forms.dtd @@ -22,6 +22,7 @@ + @@ -59,5 +60,5 @@ - + diff --git a/dspace/config/submission-forms.xml b/dspace/config/submission-forms.xml index c3296d4df9..52d4cf4ce9 100644 --- a/dspace/config/submission-forms.xml +++ b/dspace/config/submission-forms.xml @@ -14,7 +14,7 @@ - + diff --git a/pom.xml b/pom.xml index 5ff109d699..72ca722e47 100644 --- a/pom.xml +++ b/pom.xml @@ -16,10 +16,6 @@ http://www.dspace.org - - 3.0 - - UTF-8 ${project.build.sourceEncoding} @@ -55,7 +51,8 @@ (NOTE: individual POMs can override specific settings). --> - + org.apache.maven.plugins maven-enforcer-plugin @@ -71,6 +68,9 @@ ${java.version} + + [3.0.5,) +