diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 849fbf93be..3605531adb 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -20,11 +20,8 @@ List of changes in this PR: _This checklist provides a reminder of what we are going to look for when reviewing your PR. You need not complete this checklist prior to creating your PR (draft PRs are always welcome). If you are unsure about an item in the checklist, don't hesitate to ask. We're here to help!_ - [ ] My PR is small in size (e.g. less than 1,000 lines of code, not including comments & integration tests). Exceptions may be made if previously agreed upon. -- [ ] My PR passes Checkstyle validation based on the [Code Style Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Style+Guide) +- [ ] My PR passes Checkstyle validation based on the [Code Style Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Style+Guide). - [ ] My PR includes Javadoc for _all new (or modified) public methods and classes_. It also includes Javadoc for large or complex private methods. -- [ ] My PR passes all tests and includes new/updated Unit or Integration Tests for any bug fixes, improvements or new features. A few reminders about what constitutes good tests: - * Include tests for different user types, including: (1) Anonymous user, (2) Logged in user (non-admin), and (3) Administrator. - * Include tests for known error scenarios and error codes (e.g. `400 Bad Request`, `401 Unauthorized`, `403 Forbidden`, `404 Not Found`, etc) - * For bug fixes, include a test that reproduces the bug and proves it is fixed. For clarity, it may be useful to provide the test in a separate commit from the bug fix. +- [ ] My PR passes all tests and includes new/updated Unit or Integration Tests based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide). - [ ] If my PR includes new, third-party dependencies (in any `pom.xml`), I've made sure their licenses align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/master/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation. - [ ] If my PR modifies the REST API, I've linked to the REST Contract page (or open PR) related to this change. diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index c714746337..a0714c04ee 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -98,20 +98,6 @@ - - - com.mycila - license-maven-plugin - - - **/src/test/resources/** - **/src/test/data/** - **/.gitignore - **/src/main/resources/rebel.xml - src/test/data/dspaceFolder/config/spiders/** - - - org.codehaus.mojo diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java index 55bb3fed4b..ad7824bebf 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java @@ -8,14 +8,10 @@ package org.dspace.app.bulkedit; import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; -import java.io.OutputStreamWriter; import java.io.Serializable; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -27,6 +23,7 @@ import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.dspace.authority.AuthorityValue; import org.dspace.authority.factory.AuthorityServiceFactory; @@ -141,18 +138,18 @@ public class DSpaceCSV implements Serializable { /** * Create a new instance, reading the lines in from file * - * @param f The file to read from + * @param inputStream the inputstream to read from * @param c The DSpace Context * @throws Exception thrown if there is an error reading or processing the file */ - public DSpaceCSV(File f, Context c) throws Exception { + public DSpaceCSV(InputStream inputStream, Context c) throws Exception { // Initialise the class init(); // Open the CSV file BufferedReader input = null; try { - input = new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF-8")); + input = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); // Read the heading line String head = input.readLine(); @@ -623,21 +620,15 @@ public class DSpaceCSV implements Serializable { } /** - * Save the CSV file to the given filename - * - * @param filename The filename to save the CSV file to - * @throws IOException Thrown if an error occurs when writing the file + * Creates and returns an InputStream from the CSV Lines in this DSpaceCSV + * @return The InputStream created from the CSVLines in this DSpaceCSV */ - public final void save(String filename) throws IOException { - // Save the file - BufferedWriter out = new BufferedWriter( - new OutputStreamWriter( - new FileOutputStream(filename), "UTF-8")); + public InputStream getInputStream() { + StringBuilder stringBuilder = new StringBuilder(); for (String csvLine : getCSVLinesAsStringArray()) { - out.write(csvLine + "\n"); + stringBuilder.append(csvLine + "\n"); } - out.flush(); - out.close(); + return IOUtils.toInputStream(stringBuilder.toString(), StandardCharsets.UTF_8); } /** diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java index bc015ef5e0..2e4f333820 100644 --- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java @@ -8,271 +8,84 @@ package org.dspace.app.bulkedit; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import com.google.common.collect.Iterators; -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.CommandLineParser; -import org.apache.commons.cli.HelpFormatter; -import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; -import org.apache.commons.cli.PosixParser; -import org.dspace.content.Collection; -import org.dspace.content.Community; -import org.dspace.content.DSpaceObject; -import org.dspace.content.Item; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.ItemService; -import org.dspace.core.Constants; +import org.dspace.content.service.MetadataDSpaceCsvExportService; import org.dspace.core.Context; -import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.EPersonService; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.utils.DSpace; /** * Metadata exporter to allow the batch export of metadata into a file * * @author Stuart Lewis */ -public class MetadataExport { - /** - * The items to export - */ - protected Iterator toExport; +public class MetadataExport extends DSpaceRunnable { - protected ItemService itemService; + private boolean help = false; + private String filename = null; + private String handle = null; + private boolean exportAllMetadata = false; + private boolean exportAllItems = false; - protected Context context; + private static final String EXPORT_CSV = "exportCSV"; - /** - * Whether to export all metadata, or just normally edited metadata - */ - protected boolean exportAll; + private MetadataDSpaceCsvExportService metadataDSpaceCsvExportService = new DSpace().getServiceManager() + .getServicesByType(MetadataDSpaceCsvExportService.class).get(0); - protected MetadataExport() { - itemService = ContentServiceFactory.getInstance().getItemService(); - } + private EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); - /** - * Set up a new metadata export - * - * @param c The Context - * @param toExport The ItemIterator of items to export - * @param exportAll whether to export all metadata or not (include handle, provenance etc) - */ - public MetadataExport(Context c, Iterator toExport, boolean exportAll) { - itemService = ContentServiceFactory.getInstance().getItemService(); - - // Store the export settings - this.toExport = toExport; - this.exportAll = exportAll; - this.context = c; - } - - /** - * Method to export a community (and sub-communities and collections) - * - * @param c The Context - * @param toExport The Community to export - * @param exportAll whether to export all metadata or not (include handle, provenance etc) - */ - public MetadataExport(Context c, Community toExport, boolean exportAll) { - itemService = ContentServiceFactory.getInstance().getItemService(); + @Override + public void internalRun() throws Exception { + if (help) { + handler.logInfo("\nfull export: metadata-export -f filename"); + handler.logInfo("partial export: metadata-export -i handle -f filename"); + printHelp(); + return; + } + Context context = new Context(); + context.turnOffAuthorisationSystem(); try { - // Try to export the community - this.toExport = buildFromCommunity(c, toExport, 0); - this.exportAll = exportAll; - this.context = c; - } catch (SQLException sqle) { - // Something went wrong... - System.err.println("Error running exporter:"); - sqle.printStackTrace(System.err); - System.exit(1); + context.setCurrentUser(ePersonService.find(context, this.getEpersonIdentifier())); + } catch (SQLException e) { + handler.handleException(e); } + DSpaceCSV dSpaceCSV = metadataDSpaceCsvExportService + .handleExport(context, exportAllItems, exportAllMetadata, handle, + handler); + handler.writeFilestream(context, filename, dSpaceCSV.getInputStream(), EXPORT_CSV); + context.restoreAuthSystemState(); + context.complete(); } - /** - * Build an array list of item ids that are in a community (include sub-communities and collections) - * - * @param context DSpace context - * @param community The community to build from - * @param indent How many spaces to use when writing out the names of items added - * @return The list of item ids - * @throws SQLException if database error - */ - protected Iterator buildFromCommunity(Context context, Community community, int indent) - throws SQLException { - // Add all the collections - List collections = community.getCollections(); - Iterator result = null; - for (Collection collection : collections) { - for (int i = 0; i < indent; i++) { - System.out.print(" "); - } - - Iterator items = itemService.findByCollection(context, collection); - result = addItemsToResult(result, items); - - } - // Add all the sub-communities - List communities = community.getSubcommunities(); - for (Community subCommunity : communities) { - for (int i = 0; i < indent; i++) { - System.out.print(" "); - } - Iterator items = buildFromCommunity(context, subCommunity, indent + 1); - result = addItemsToResult(result, items); - } - - return result; + @Override + public MetadataExportScriptConfiguration getScriptConfiguration() { + return new DSpace().getServiceManager().getServiceByName("metadata-export", + MetadataExportScriptConfiguration.class); } - private Iterator addItemsToResult(Iterator result, Iterator items) { - if (result == null) { - result = items; - } else { - result = Iterators.concat(result, items); - } + @Override + public void setup() throws ParseException { - return result; - } - - /** - * Run the export - * - * @return the exported CSV lines - */ - public DSpaceCSV export() { - try { - Context.Mode originalMode = context.getCurrentMode(); - context.setMode(Context.Mode.READ_ONLY); - - // Process each item - DSpaceCSV csv = new DSpaceCSV(exportAll); - while (toExport.hasNext()) { - Item item = toExport.next(); - csv.addItem(item); - context.uncacheEntity(item); - } - - context.setMode(originalMode); - // Return the results - return csv; - } catch (Exception e) { - // Something went wrong... - System.err.println("Error exporting to CSV:"); - e.printStackTrace(); - return null; - } - } - - /** - * Print the help message - * - * @param options The command line options the user gave - * @param exitCode the system exit code to use - */ - private static void printHelp(Options options, int exitCode) { - // print the help message - HelpFormatter myhelp = new HelpFormatter(); - myhelp.printHelp("MetadataExport\n", options); - System.out.println("\nfull export: metadataexport -f filename"); - System.out.println("partial export: metadataexport -i handle -f filename"); - System.exit(exitCode); - } - - /** - * main method to run the metadata exporter - * - * @param argv the command line arguments given - * @throws Exception if error occurs - */ - public static void main(String[] argv) throws Exception { - // Create an options object and populate it - CommandLineParser parser = new PosixParser(); - - Options options = new Options(); - - options.addOption("i", "id", true, "ID or handle of thing to export (item, collection, or community)"); - options.addOption("f", "file", true, "destination where you want file written"); - options.addOption("a", "all", false, - "include all metadata fields that are not normally changed (e.g. provenance)"); - options.addOption("h", "help", false, "help"); - - CommandLine line = null; - - try { - line = parser.parse(options, argv); - } catch (ParseException pe) { - System.err.println("Error with commands."); - printHelp(options, 1); - System.exit(0); - } - - if (line.hasOption('h')) { - printHelp(options, 0); + if (commandLine.hasOption('h')) { + help = true; + return; } // Check a filename is given - if (!line.hasOption('f')) { - System.err.println("Required parameter -f missing!"); - printHelp(options, 1); + if (!commandLine.hasOption('f')) { + throw new ParseException("Required parameter -f missing!"); } - String filename = line.getOptionValue('f'); + filename = commandLine.getOptionValue('f'); - // Create a context - Context c = new Context(Context.Mode.READ_ONLY); - c.turnOffAuthorisationSystem(); + exportAllMetadata = commandLine.hasOption('a'); - // The things we'll export - Iterator toExport = null; - MetadataExport exporter = null; - - // Export everything? - boolean exportAll = line.hasOption('a'); - - ContentServiceFactory contentServiceFactory = ContentServiceFactory.getInstance(); - // Check we have an item OK - ItemService itemService = contentServiceFactory.getItemService(); - if (!line.hasOption('i')) { - System.out.println("Exporting whole repository WARNING: May take some time!"); - exporter = new MetadataExport(c, itemService.findAll(c), exportAll); - } else { - String handle = line.getOptionValue('i'); - DSpaceObject dso = HandleServiceFactory.getInstance().getHandleService().resolveToObject(c, handle); - if (dso == null) { - System.err.println("Item '" + handle + "' does not resolve to an item in your repository!"); - printHelp(options, 1); - } - - if (dso.getType() == Constants.ITEM) { - System.out.println("Exporting item '" + dso.getName() + "' (" + handle + ")"); - List item = new ArrayList<>(); - item.add((Item) dso); - exporter = new MetadataExport(c, item.iterator(), exportAll); - } else if (dso.getType() == Constants.COLLECTION) { - System.out.println("Exporting collection '" + dso.getName() + "' (" + handle + ")"); - Collection collection = (Collection) dso; - toExport = itemService.findByCollection(c, collection); - exporter = new MetadataExport(c, toExport, exportAll); - } else if (dso.getType() == Constants.COMMUNITY) { - System.out.println("Exporting community '" + dso.getName() + "' (" + handle + ")"); - exporter = new MetadataExport(c, (Community) dso, exportAll); - } else { - System.err.println("Error identifying '" + handle + "'"); - System.exit(1); - } + if (!commandLine.hasOption('i')) { + exportAllItems = true; } - - // Perform the export - DSpaceCSV csv = exporter.export(); - - // Save the files to the file - csv.save(filename); - - // Finish off and tidy up - c.restoreAuthSystemState(); - c.complete(); + handle = commandLine.getOptionValue('i'); } } diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportScriptConfiguration.java new file mode 100644 index 0000000000..65c0ddd8cf --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExportScriptConfiguration.java @@ -0,0 +1,74 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.bulkedit; + +import java.io.OutputStream; +import java.sql.SQLException; + +import org.apache.commons.cli.Options; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.core.Context; +import org.dspace.scripts.configuration.ScriptConfiguration; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * The {@link ScriptConfiguration} for the {@link MetadataExport} script + */ +public class MetadataExportScriptConfiguration extends ScriptConfiguration { + + @Autowired + private AuthorizeService authorizeService; + + private Class dspaceRunnableClass; + + @Override + public Class getDspaceRunnableClass() { + return dspaceRunnableClass; + } + + /** + * Generic setter for the dspaceRunnableClass + * @param dspaceRunnableClass The dspaceRunnableClass to be set on this MetadataExportScriptConfiguration + */ + @Override + public void setDspaceRunnableClass(Class dspaceRunnableClass) { + this.dspaceRunnableClass = dspaceRunnableClass; + } + + @Override + public boolean isAllowedToExecute(Context context) { + try { + return authorizeService.isAdmin(context); + } catch (SQLException e) { + throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); + } + } + + @Override + public Options getOptions() { + if (options == null) { + Options options = new Options(); + + options.addOption("i", "id", true, "ID or handle of thing to export (item, collection, or community)"); + options.getOption("i").setType(String.class); + options.addOption("f", "file", true, "destination where you want file written"); + options.getOption("f").setType(OutputStream.class); + options.getOption("f").setRequired(true); + options.addOption("a", "all", false, + "include all metadata fields that are not normally changed (e.g. provenance)"); + options.getOption("a").setType(boolean.class); + options.addOption("h", "help", false, "help"); + options.getOption("h").setType(boolean.class); + + + super.options = options; + } + return options; + } + +} 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 e8fff71cf4..eb0a4e2935 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 @@ -7,10 +7,8 @@ */ package org.dspace.app.bulkedit; -import java.io.BufferedReader; -import java.io.File; import java.io.IOException; -import java.io.InputStreamReader; +import java.io.InputStream; import java.sql.SQLException; import java.util.ArrayList; import java.util.Enumeration; @@ -19,16 +17,12 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.UUID; import javax.annotation.Nullable; -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.CommandLineParser; -import org.apache.commons.cli.HelpFormatter; -import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; -import org.apache.commons.cli.PosixParser; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.authority.AuthorityValue; @@ -65,6 +59,10 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.handle.service.HandleService; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.scripts.handler.DSpaceRunnableHandler; +import org.dspace.utils.DSpace; +import org.dspace.workflow.WorkflowException; import org.dspace.workflow.WorkflowItem; import org.dspace.workflow.WorkflowService; import org.dspace.workflow.factory.WorkflowServiceFactory; @@ -74,11 +72,7 @@ import org.dspace.workflow.factory.WorkflowServiceFactory; * * @author Stuart Lewis */ -public class MetadataImport { - /** - * The Context - */ - Context c; +public class MetadataImport extends DSpaceRunnable { /** * The DSpaceCSV object we're processing @@ -95,10 +89,6 @@ public class MetadataImport { */ protected static Set authorityControlled; - static { - setAuthorizedMetadataFields(); - } - /** * The prefix of the authority controlled field */ @@ -143,45 +133,208 @@ public class MetadataImport { */ protected Integer rowCount = 1; + private boolean useTemplate = false; + private String filename = null; + private boolean useWorkflow = false; + private boolean workflowNotify = false; + private boolean change = false; + private boolean help = false; + protected boolean validateOnly; + /** * Logger */ protected static final Logger log = org.apache.logging.log4j.LogManager.getLogger(MetadataImport.class); - protected final AuthorityValueService authorityValueService; - - protected final ItemService itemService; - protected final InstallItemService installItemService; - protected final CollectionService collectionService; - protected final HandleService handleService; - protected final WorkspaceItemService workspaceItemService; - protected final RelationshipTypeService relationshipTypeService; - protected final RelationshipService relationshipService; - protected final EntityTypeService entityTypeService; - protected final EntityService entityService; + protected ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + protected InstallItemService installItemService = ContentServiceFactory.getInstance().getInstallItemService(); + protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); + protected HandleService handleService = HandleServiceFactory.getInstance().getHandleService(); + protected WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); + protected RelationshipTypeService relationshipTypeService = ContentServiceFactory.getInstance() + .getRelationshipTypeService(); + protected RelationshipService relationshipService = ContentServiceFactory.getInstance().getRelationshipService(); + protected EntityTypeService entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService(); + protected EntityService entityService = ContentServiceFactory.getInstance().getEntityService(); + protected AuthorityValueService authorityValueService = AuthorityServiceFactory.getInstance() + .getAuthorityValueService(); /** * Create an instance of the metadata importer. Requires a context and an array of CSV lines * to examine. * - * @param c The context * @param toImport An array of CSV lines to examine */ - public MetadataImport(Context c, DSpaceCSV toImport) { + public void initMetadataImport(DSpaceCSV toImport) { // Store the import settings - this.c = c; - csv = toImport; this.toImport = toImport.getCSVLines(); - installItemService = ContentServiceFactory.getInstance().getInstallItemService(); - itemService = ContentServiceFactory.getInstance().getItemService(); - collectionService = ContentServiceFactory.getInstance().getCollectionService(); - handleService = HandleServiceFactory.getInstance().getHandleService(); - authorityValueService = AuthorityServiceFactory.getInstance().getAuthorityValueService(); - workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); - relationshipService = ContentServiceFactory.getInstance().getRelationshipService(); - relationshipTypeService = ContentServiceFactory.getInstance().getRelationshipTypeService(); - entityTypeService = ContentServiceFactory.getInstance().getEntityTypeService(); - entityService = ContentServiceFactory.getInstance().getEntityService(); + } + + @Override + public void internalRun() throws Exception { + if (help) { + printHelp(); + return; + } + // Create a context + Context c = null; + c = new Context(); + c.turnOffAuthorisationSystem(); + + // Find the EPerson, assign to context + try { + if (commandLine.hasOption('e')) { + EPerson eperson; + String e = commandLine.getOptionValue('e'); + if (e.indexOf('@') != -1) { + eperson = EPersonServiceFactory.getInstance().getEPersonService().findByEmail(c, e); + } else { + eperson = EPersonServiceFactory.getInstance().getEPersonService().find(c, UUID.fromString(e)); + } + + if (eperson == null) { + throw new ParseException("Error, eperson cannot be found: " + e); + } + c.setCurrentUser(eperson); + } + } catch (Exception e) { + throw new ParseException("Unable to find DSpace user: " + e.getMessage()); + } + + if (authorityControlled == null) { + setAuthorizedMetadataFields(); + } + // Read commandLines from the CSV file + try { + + Optional optionalFileStream = handler.getFileStream(c, filename); + if (optionalFileStream.isPresent()) { + csv = new DSpaceCSV(optionalFileStream.get(), c); + } else { + throw new IllegalArgumentException("Error reading file, the file couldn't be found for filename: " + + filename); + } + } catch (MetadataImportInvalidHeadingException miihe) { + throw miihe; + } catch (Exception e) { + throw new Exception("Error reading file: " + e.getMessage(), e); + } + + // Perform the first import - just highlight differences + initMetadataImport(csv); + List changes; + + if (!commandLine.hasOption('s') || validateOnly) { + // See what has changed + try { + changes = runImport(c, false, useWorkflow, workflowNotify, useTemplate); + } catch (MetadataImportException mie) { + throw mie; + } + + // Display the changes + int changeCounter = displayChanges(changes, false); + + // If there were changes, ask if we should execute them + if (!validateOnly && changeCounter > 0) { + try { + // Ask the user if they want to make the changes + handler.logInfo("\n" + changeCounter + " item(s) will be changed\n"); + change = determineChange(handler); + + } catch (IOException ioe) { + throw new IOException("Error: " + ioe.getMessage() + ", No changes have been made", ioe); + } + } else { + handler.logInfo("There were no changes detected"); + } + } else { + change = true; + } + + try { + // If required, make the change + if (change && !validateOnly) { + try { + // Make the changes + changes = runImport(c, true, useWorkflow, workflowNotify, useTemplate); + } catch (MetadataImportException mie) { + throw mie; + } + + // Display the changes + displayChanges(changes, true); + } + + // Finsh off and tidy up + c.restoreAuthSystemState(); + c.complete(); + } catch (Exception e) { + c.abort(); + throw new Exception( + "Error committing changes to database: " + e.getMessage() + ", aborting most recent changes", e); + } + + } + + /** + * This method determines whether the changes should be applied or not. This is default set to true for the REST + * script as we don't want to interact with the caller. This will be overwritten in the CLI script to ask for + * confirmation + * @param handler Applicable DSpaceRunnableHandler + * @return boolean indicating the value + * @throws IOException If something goes wrong + */ + protected boolean determineChange(DSpaceRunnableHandler handler) throws IOException { + return true; + } + + @Override + public MetadataImportScriptConfiguration getScriptConfiguration() { + return new DSpace().getServiceManager().getServiceByName("metadata-import", + MetadataImportScriptConfiguration.class); + } + + + public void setup() throws ParseException { + useTemplate = false; + filename = null; + useWorkflow = false; + workflowNotify = false; + + if (commandLine.hasOption('h')) { + help = true; + return; + } + + // Check a filename is given + if (!commandLine.hasOption('f')) { + throw new ParseException("Required parameter -f missing!"); + } + filename = commandLine.getOptionValue('f'); + if (!commandLine.hasOption('e')) { + throw new ParseException("Required parameter -e missing!"); + } + + // Option to apply template to new items + if (commandLine.hasOption('t')) { + useTemplate = true; + } + + // Options for workflows, and workflow notifications for new items + if (commandLine.hasOption('w')) { + useWorkflow = true; + if (commandLine.hasOption('n')) { + workflowNotify = true; + } + } else if (commandLine.hasOption('n')) { + throw new ParseException( + "Invalid option 'n': (notify) can only be specified with the 'w' (workflow) option."); + } + validateOnly = commandLine.hasOption('v'); + + // Is this a silent run? + change = false; } /** @@ -195,281 +348,277 @@ public class MetadataImport { * @return An array of BulkEditChange elements representing the items that have changed * @throws MetadataImportException if something goes wrong */ - public List runImport(boolean change, + public List runImport(Context c, boolean change, boolean useWorkflow, boolean workflowNotify, - boolean useTemplate) throws MetadataImportException { + boolean useTemplate) + throws MetadataImportException, SQLException, AuthorizeException, WorkflowException, IOException { // Store the changes ArrayList changes = new ArrayList(); // Make the changes - try { - Context.Mode originalMode = c.getCurrentMode(); - c.setMode(Context.Mode.BATCH_EDIT); + Context.Mode originalMode = c.getCurrentMode(); + c.setMode(Context.Mode.BATCH_EDIT); - // Process each change - rowCount = 1; - for (DSpaceCSVLine line : toImport) { - // Resolve target references to other items - populateRefAndRowMap(line, line.getID()); - line = resolveEntityRefs(line); - // Get the DSpace item to compare with - UUID id = line.getID(); + // Process each change + rowCount = 1; + for (DSpaceCSVLine line : toImport) { + // Resolve target references to other items + populateRefAndRowMap(line, line.getID()); + line = resolveEntityRefs(c, line); + // Get the DSpace item to compare with + UUID id = line.getID(); - // Is there an action column? - if (csv.hasActions() && (!"".equals(line.getAction())) && (id == null)) { - throw new MetadataImportException("'action' not allowed for new items!"); - } - - WorkspaceItem wsItem = null; - WorkflowItem wfItem = null; - Item item = null; - - // Is this an existing item? - if (id != null) { - // Get the item - item = itemService.find(c, id); - if (item == null) { - throw new MetadataImportException("Unknown item ID " + id); - } - - // Record changes - BulkEditChange whatHasChanged = new BulkEditChange(item); - - // Has it moved collection? - List collections = line.get("collection"); - if (collections != null) { - // Sanity check we're not orphaning it - if (collections.size() == 0) { - throw new MetadataImportException("Missing collection from item " + item.getHandle()); - } - List actualCollections = item.getCollections(); - compare(item, collections, actualCollections, whatHasChanged, change); - } - - // Iterate through each metadata element in the csv line - for (String md : line.keys()) { - // Get the values we already have - if (!"id".equals(md)) { - // Get the values from the CSV - String[] fromCSV = line.get(md).toArray(new String[line.get(md).size()]); - // Remove authority unless the md is not authority controlled - if (!isAuthorityControlledField(md)) { - for (int i = 0; i < fromCSV.length; i++) { - int pos = fromCSV[i].indexOf(csv.getAuthoritySeparator()); - if (pos > -1) { - fromCSV[i] = fromCSV[i].substring(0, pos); - } - } - } - // Compare - compareAndUpdate(item, fromCSV, change, md, whatHasChanged, line); - } - } - - if (csv.hasActions()) { - // Perform the action - String action = line.getAction(); - if ("".equals(action)) { - // Do nothing - } else if ("expunge".equals(action)) { - // Does the configuration allow deletes? - if (!ConfigurationManager.getBooleanProperty("bulkedit", "allowexpunge", false)) { - throw new MetadataImportException("'expunge' action denied by configuration"); - } - - // Remove the item - - if (change) { - itemService.delete(c, item); - } - - whatHasChanged.setDeleted(); - } else if ("withdraw".equals(action)) { - // Withdraw the item - if (!item.isWithdrawn()) { - if (change) { - itemService.withdraw(c, item); - } - whatHasChanged.setWithdrawn(); - } - } else if ("reinstate".equals(action)) { - // Reinstate the item - if (item.isWithdrawn()) { - if (change) { - itemService.reinstate(c, item); - } - whatHasChanged.setReinstated(); - } - } else { - // Unknown action! - throw new MetadataImportException("Unknown action: " + action); - } - } - - // Only record if changes have been made - if (whatHasChanged.hasChanges()) { - changes.add(whatHasChanged); - } - } else { - // This is marked as a new item, so no need to compare - - // First check a user is set, otherwise this can't happen - if (c.getCurrentUser() == null) { - throw new MetadataImportException( - "When adding new items, a user must be specified with the -e option"); - } - - // Iterate through each metadata element in the csv line - BulkEditChange whatHasChanged = new BulkEditChange(); - for (String md : line.keys()) { - // Get the values we already have - if (!"id".equals(md) && !"rowName".equals(md)) { - // Get the values from the CSV - String[] fromCSV = line.get(md).toArray(new String[line.get(md).size()]); - - // Remove authority unless the md is not authority controlled - if (!isAuthorityControlledField(md)) { - for (int i = 0; i < fromCSV.length; i++) { - int pos = fromCSV[i].indexOf(csv.getAuthoritySeparator()); - if (pos > -1) { - fromCSV[i] = fromCSV[i].substring(0, pos); - } - } - } - - // Add all the values from the CSV line - add(fromCSV, md, whatHasChanged); - } - } - - // Check it has an owning collection - List collections = line.get("collection"); - if (collections == null) { - throw new MetadataImportException( - "New items must have a 'collection' assigned in the form of a handle"); - } - - // Check collections are really collections - ArrayList check = new ArrayList(); - Collection collection; - for (String handle : collections) { - try { - // Resolve the handle to the collection - collection = (Collection) handleService.resolveToObject(c, handle); - - // Check it resolved OK - if (collection == null) { - throw new MetadataImportException( - "'" + handle + "' is not a Collection! You must specify a valid collection for " + - "new items"); - } - - // Check for duplicate - if (check.contains(collection)) { - throw new MetadataImportException( - "Duplicate collection assignment detected in new item! " + handle); - } else { - check.add(collection); - } - } catch (Exception ex) { - throw new MetadataImportException( - "'" + handle + "' is not a Collection! You must specify a valid collection for new " + - "items", - ex); - } - } - - // Record the addition to collections - boolean first = true; - for (String handle : collections) { - Collection extra = (Collection) handleService.resolveToObject(c, handle); - if (first) { - whatHasChanged.setOwningCollection(extra); - } else { - whatHasChanged.registerNewMappedCollection(extra); - } - first = false; - } - - // Create the new item? - if (change) { - // Create the item - String collectionHandle = line.get("collection").get(0); - collection = (Collection) handleService.resolveToObject(c, collectionHandle); - wsItem = workspaceItemService.create(c, collection, useTemplate); - item = wsItem.getItem(); - - // Add the metadata to the item - for (BulkEditMetadataValue dcv : whatHasChanged.getAdds()) { - 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()) { - if (StringUtils.equals(dcv.getSchema(), MetadataSchemaEnum.RELATION.getName())) { - addRelationship(c, item, dcv.getElement(), dcv.getValue()); - } - } - - - // Should the workflow be used? - if (useWorkflow) { - WorkflowService workflowService = WorkflowServiceFactory.getInstance().getWorkflowService(); - if (workflowNotify) { - wfItem = workflowService.start(c, wsItem); - } else { - wfItem = workflowService.startWithoutNotify(c, wsItem); - } - } else { - // Install the item - installItemService.installItem(c, wsItem); - } - - // Add to extra collections - if (line.get("collection").size() > 0) { - for (int i = 1; i < collections.size(); i++) { - String handle = collections.get(i); - Collection extra = (Collection) handleService.resolveToObject(c, handle); - collectionService.addItem(c, extra, item); - } - } - - whatHasChanged.setItem(item); - } - - // Record the changes - changes.add(whatHasChanged); - } - - if (change) { - //only clear cache if changes have been made. - c.uncacheEntity(wsItem); - c.uncacheEntity(wfItem); - c.uncacheEntity(item); - } - populateRefAndRowMap(line, item == null ? null : item.getID()); - // keep track of current rows processed - rowCount++; + // Is there an action column? + if (csv.hasActions() && (!"".equals(line.getAction())) && (id == null)) { + throw new MetadataImportException("'action' not allowed for new items!"); } - c.setMode(originalMode); - } catch (MetadataImportException mie) { - throw mie; - } catch (Exception e) { - e.printStackTrace(); + WorkspaceItem wsItem = null; + WorkflowItem wfItem = null; + Item item = null; + + // Is this an existing item? + if (id != null) { + // Get the item + item = itemService.find(c, id); + if (item == null) { + throw new MetadataImportException("Unknown item ID " + id); + } + + // Record changes + BulkEditChange whatHasChanged = new BulkEditChange(item); + + // Has it moved collection? + List collections = line.get("collection"); + if (collections != null) { + // Sanity check we're not orphaning it + if (collections.size() == 0) { + throw new MetadataImportException("Missing collection from item " + item.getHandle()); + } + List actualCollections = item.getCollections(); + compare(c, item, collections, actualCollections, whatHasChanged, change); + } + + // Iterate through each metadata element in the csv line + for (String md : line.keys()) { + // Get the values we already have + if (!"id".equals(md)) { + // Get the values from the CSV + String[] fromCSV = line.get(md).toArray(new String[line.get(md).size()]); + // Remove authority unless the md is not authority controlled + if (!isAuthorityControlledField(md)) { + for (int i = 0; i < fromCSV.length; i++) { + int pos = fromCSV[i].indexOf(csv.getAuthoritySeparator()); + if (pos > -1) { + fromCSV[i] = fromCSV[i].substring(0, pos); + } + } + } + // Compare + compareAndUpdate(c, item, fromCSV, change, md, whatHasChanged, line); + } + } + + if (csv.hasActions()) { + // Perform the action + String action = line.getAction(); + if ("".equals(action)) { + // Do nothing + } else if ("expunge".equals(action)) { + // Does the configuration allow deletes? + if (!ConfigurationManager.getBooleanProperty("bulkedit", "allowexpunge", false)) { + throw new MetadataImportException("'expunge' action denied by configuration"); + } + + // Remove the item + + if (change) { + itemService.delete(c, item); + } + + whatHasChanged.setDeleted(); + } else if ("withdraw".equals(action)) { + // Withdraw the item + if (!item.isWithdrawn()) { + if (change) { + itemService.withdraw(c, item); + } + whatHasChanged.setWithdrawn(); + } + } else if ("reinstate".equals(action)) { + // Reinstate the item + if (item.isWithdrawn()) { + if (change) { + itemService.reinstate(c, item); + } + whatHasChanged.setReinstated(); + } + } else { + // Unknown action! + throw new MetadataImportException("Unknown action: " + action); + } + } + + // Only record if changes have been made + if (whatHasChanged.hasChanges()) { + changes.add(whatHasChanged); + } + } else { + // This is marked as a new item, so no need to compare + + // First check a user is set, otherwise this can't happen + if (c.getCurrentUser() == null) { + throw new MetadataImportException( + "When adding new items, a user must be specified with the -e option"); + } + + // Iterate through each metadata element in the csv line + BulkEditChange whatHasChanged = new BulkEditChange(); + for (String md : line.keys()) { + // Get the values we already have + if (!"id".equals(md) && !"rowName".equals(md)) { + // Get the values from the CSV + String[] fromCSV = line.get(md).toArray(new String[line.get(md).size()]); + + // Remove authority unless the md is not authority controlled + if (!isAuthorityControlledField(md)) { + for (int i = 0; i < fromCSV.length; i++) { + int pos = fromCSV[i].indexOf(csv.getAuthoritySeparator()); + if (pos > -1) { + fromCSV[i] = fromCSV[i].substring(0, pos); + } + } + } + + // Add all the values from the CSV line + add(c, fromCSV, md, whatHasChanged); + } + } + + // Check it has an owning collection + List collections = line.get("collection"); + if (collections == null) { + throw new MetadataImportException( + "New items must have a 'collection' assigned in the form of a handle"); + } + + // Check collections are really collections + ArrayList check = new ArrayList(); + Collection collection; + for (String handle : collections) { + try { + // Resolve the handle to the collection + collection = (Collection) handleService.resolveToObject(c, handle); + + // Check it resolved OK + if (collection == null) { + throw new MetadataImportException( + "'" + handle + "' is not a Collection! You must specify a valid collection for " + + "new items"); + } + + // Check for duplicate + if (check.contains(collection)) { + throw new MetadataImportException( + "Duplicate collection assignment detected in new item! " + handle); + } else { + check.add(collection); + } + } catch (Exception ex) { + throw new MetadataImportException( + "'" + handle + "' is not a Collection! You must specify a valid collection for new " + + "items", + ex); + } + } + + // Record the addition to collections + boolean first = true; + for (String handle : collections) { + Collection extra = (Collection) handleService.resolveToObject(c, handle); + if (first) { + whatHasChanged.setOwningCollection(extra); + } else { + whatHasChanged.registerNewMappedCollection(extra); + } + first = false; + } + + // Create the new item? + if (change) { + // Create the item + String collectionHandle = line.get("collection").get(0); + collection = (Collection) handleService.resolveToObject(c, collectionHandle); + wsItem = workspaceItemService.create(c, collection, useTemplate); + item = wsItem.getItem(); + + // Add the metadata to the item + for (BulkEditMetadataValue dcv : whatHasChanged.getAdds()) { + 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()) { + if (StringUtils.equals(dcv.getSchema(), MetadataSchemaEnum.RELATION.getName())) { + addRelationship(c, item, dcv.getElement(), dcv.getValue()); + } + } + + + // Should the workflow be used? + if (useWorkflow) { + WorkflowService workflowService = WorkflowServiceFactory.getInstance().getWorkflowService(); + if (workflowNotify) { + wfItem = workflowService.start(c, wsItem); + } else { + wfItem = workflowService.startWithoutNotify(c, wsItem); + } + } else { + // Install the item + installItemService.installItem(c, wsItem); + } + + // Add to extra collections + if (line.get("collection").size() > 0) { + for (int i = 1; i < collections.size(); i++) { + String handle = collections.get(i); + Collection extra = (Collection) handleService.resolveToObject(c, handle); + collectionService.addItem(c, extra, item); + } + } + + whatHasChanged.setItem(item); + } + + // Record the changes + changes.add(whatHasChanged); + } + + if (change) { + //only clear cache if changes have been made. + c.uncacheEntity(wsItem); + c.uncacheEntity(wfItem); + c.uncacheEntity(item); + } + populateRefAndRowMap(line, item == null ? null : item.getID()); + // keep track of current rows processed + rowCount++; } + c.setMode(originalMode); + + // Return the changes - if (!change ) { - validateExpressedRelations(); + if (!change) { + validateExpressedRelations(c); } return changes; } @@ -487,7 +636,7 @@ public class MetadataImport { * @throws AuthorizeException if there is an authorization problem with permissions * @throws MetadataImportException custom exception for error handling within metadataimport */ - protected void compareAndUpdate(Item item, String[] fromCSV, boolean change, + protected void compareAndUpdate(Context c, Item item, String[] fromCSV, boolean change, String md, BulkEditChange changes, DSpaceCSVLine line) throws SQLException, AuthorizeException, MetadataImportException { // Log what metadata element we're looking at @@ -565,7 +714,7 @@ public class MetadataImport { // Compare from current->csv for (int v = 0; v < fromCSV.length; v++) { String value = fromCSV[v]; - BulkEditMetadataValue dcv = getBulkEditValueFromCSV(language, schema, element, qualifier, value, + BulkEditMetadataValue dcv = getBulkEditValueFromCSV(c, language, schema, element, qualifier, value, fromAuthority); if (fromAuthority != null) { value = dcv.getValue() + csv.getAuthoritySeparator() + dcv.getAuthority() + csv @@ -694,8 +843,8 @@ public class MetadataImport { * @throws AuthorizeException If something goes wrong */ private void addRelationships(Context c, Item item, String typeName, List values) - throws SQLException, AuthorizeException, - MetadataImportException { + throws SQLException, AuthorizeException, + MetadataImportException { for (String value : values) { addRelationship(c, item, typeName, value); } @@ -746,22 +895,23 @@ public class MetadataImport { Entity relationEntity = getEntity(c, value); // Get relationship type of entity and item String relationEntityRelationshipType = itemService.getMetadata(relationEntity.getItem(), - "relationship", "type", - null, Item.ANY).get(0).getValue(); + "relationship", "type", + null, Item.ANY).get(0).getValue(); String itemRelationshipType = itemService.getMetadata(item, "relationship", "type", - null, Item.ANY).get(0).getValue(); + null, Item.ANY).get(0).getValue(); // Get the correct RelationshipType based on typeName List relType = relationshipTypeService.findByLeftwardOrRightwardTypeName(c, typeName); RelationshipType foundRelationshipType = matchRelationshipType(relType, - relationEntityRelationshipType, itemRelationshipType, typeName); + relationEntityRelationshipType, + itemRelationshipType, typeName); if (foundRelationshipType == null) { throw new MetadataImportException("Error on CSV row " + rowCount + ":" + "\n" + - "No Relationship type found for:\n" + - "Target type: " + relationEntityRelationshipType + "\n" + - "Origin referer type: " + itemRelationshipType + "\n" + - "with typeName: " + typeName); + "No Relationship type found for:\n" + + "Target type: " + relationEntityRelationshipType + "\n" + + "Origin referer type: " + itemRelationshipType + "\n" + + "with typeName: " + typeName); } if (foundRelationshipType.getLeftwardType().equalsIgnoreCase(typeName)) { @@ -783,7 +933,7 @@ public class MetadataImport { int leftPlace = relationshipService.findNextLeftPlaceByLeftItem(c, leftItem); int rightPlace = relationshipService.findNextRightPlaceByRightItem(c, rightItem); Relationship persistedRelationship = relationshipService.create(c, leftItem, rightItem, - foundRelationshipType, leftPlace, rightPlace); + foundRelationshipType, leftPlace, rightPlace); relationshipService.update(c, persistedRelationship); } @@ -801,7 +951,7 @@ public class MetadataImport { * @throws IOException Can be thrown when moving items in communities * @throws MetadataImportException If something goes wrong to be reported back to the user */ - protected void compare(Item item, + protected void compare(Context c, Item item, List collections, List actualCollections, BulkEditChange bechange, @@ -898,8 +1048,8 @@ public class MetadataImport { // Remove from old owned collection (if still a member) if (bechange.getOldOwningCollection() != null) { boolean found = false; - for (Collection c : item.getCollections()) { - if (c.getID().equals(bechange.getOldOwningCollection().getID())) { + for (Collection collection : item.getCollections()) { + if (collection.getID().equals(bechange.getOldOwningCollection().getID())) { found = true; } } @@ -926,7 +1076,7 @@ public class MetadataImport { * @throws SQLException when an SQL error has occurred (querying DSpace) * @throws AuthorizeException If the user can't make the changes */ - protected void add(String[] fromCSV, String md, BulkEditChange changes) + protected void add(Context c, String[] fromCSV, String md, BulkEditChange changes) throws SQLException, AuthorizeException { // Don't add owning collection or action if (("collection".equals(md)) || ("action".equals(md))) { @@ -964,7 +1114,7 @@ public class MetadataImport { // Add all the values for (String value : fromCSV) { - BulkEditMetadataValue dcv = getBulkEditValueFromCSV(language, schema, element, qualifier, value, + BulkEditMetadataValue dcv = getBulkEditValueFromCSV(c, language, schema, element, qualifier, value, fromAuthority); if (fromAuthority != null) { value = dcv.getValue() + csv.getAuthoritySeparator() + dcv.getAuthority() + csv @@ -978,7 +1128,7 @@ public class MetadataImport { } } - protected BulkEditMetadataValue getBulkEditValueFromCSV(String language, String schema, String element, + protected BulkEditMetadataValue getBulkEditValueFromCSV(Context c, String language, String schema, String element, String qualifier, String value, AuthorityValue fromAuthority) { // Look to see if it should be removed @@ -1057,20 +1207,6 @@ public class MetadataImport { return in.replaceAll("\r\n", "").replaceAll("\n", "").trim(); } - /** - * Print the help message - * - * @param options The command line options the user gave - * @param exitCode the system exit code to use - */ - private static void printHelp(Options options, int exitCode) { - // print the help message - HelpFormatter myhelp = new HelpFormatter(); - myhelp.printHelp("MetatadataImport\n", options); - System.out.println("\nmetadataimport: MetadataImport -f filename"); - System.exit(exitCode); - } - /** * Display the changes that have been detected, or that have been made * @@ -1078,7 +1214,7 @@ public class MetadataImport { * @param changed Whether or not the changes have been made * @return The number of items that have changed */ - private static int displayChanges(List changes, boolean changed) { + private int displayChanges(List changes, boolean changed) { // Display the changes int changeCounter = 0; for (BulkEditChange change : changes) { @@ -1093,20 +1229,18 @@ public class MetadataImport { (change.isDeleted()) || (change.isWithdrawn()) || (change.isReinstated())) { // Show the item Item i = change.getItem(); - - System.out.println("-----------------------------------------------------------"); + handler.logInfo("-----------------------------------------------------------"); if (!change.isNewItem()) { - System.out.println("Changes for item: " + i.getID() + " (" + i.getHandle() + ")"); + handler.logInfo("Changes for item: " + i.getID() + " (" + i.getHandle() + ")"); } else { - System.out.print("New item: "); + handler.logInfo("New item: "); if (i != null) { if (i.getHandle() != null) { - System.out.print(i.getID() + " (" + i.getHandle() + ")"); + handler.logInfo(i.getID() + " (" + i.getHandle() + ")"); } else { - System.out.print(i.getID() + " (in workflow)"); + handler.logInfo(i.getID() + " (in workflow)"); } } - System.out.println(); } changeCounter++; } @@ -1114,23 +1248,23 @@ public class MetadataImport { // Show actions if (change.isDeleted()) { if (changed) { - System.out.println(" - EXPUNGED!"); + handler.logInfo(" - EXPUNGED!"); } else { - System.out.println(" - EXPUNGE!"); + handler.logInfo(" - EXPUNGE!"); } } if (change.isWithdrawn()) { if (changed) { - System.out.println(" - WITHDRAWN!"); + handler.logInfo(" - WITHDRAWN!"); } else { - System.out.println(" - WITHDRAW!"); + handler.logInfo(" - WITHDRAW!"); } } if (change.isReinstated()) { if (changed) { - System.out.println(" - REINSTATED!"); + handler.logInfo(" - REINSTATED!"); } else { - System.out.println(" - REINSTATE!"); + handler.logInfo(" - REINSTATE!"); } } @@ -1140,11 +1274,11 @@ public class MetadataImport { String cHandle = c.getHandle(); String cName = c.getName(); if (!changed) { - System.out.print(" + New owning collection (" + cHandle + "): "); + handler.logInfo(" + New owning collection (" + cHandle + "): "); } else { - System.out.print(" + New owning collection (" + cHandle + "): "); + handler.logInfo(" + New owning collection (" + cHandle + "): "); } - System.out.println(cName); + handler.logInfo(cName); } c = change.getOldOwningCollection(); @@ -1152,11 +1286,11 @@ public class MetadataImport { String cHandle = c.getHandle(); String cName = c.getName(); if (!changed) { - System.out.print(" + Old owning collection (" + cHandle + "): "); + handler.logInfo(" + Old owning collection (" + cHandle + "): "); } else { - System.out.print(" + Old owning collection (" + cHandle + "): "); + handler.logInfo(" + Old owning collection (" + cHandle + "): "); } - System.out.println(cName); + handler.logInfo(cName); } } @@ -1165,11 +1299,11 @@ public class MetadataImport { String cHandle = c.getHandle(); String cName = c.getName(); if (!changed) { - System.out.print(" + Map to collection (" + cHandle + "): "); + handler.logInfo(" + Map to collection (" + cHandle + "): "); } else { - System.out.print(" + Mapped to collection (" + cHandle + "): "); + handler.logInfo(" + Mapped to collection (" + cHandle + "): "); } - System.out.println(cName); + handler.logInfo(cName); } // Show old mapped collections @@ -1177,11 +1311,11 @@ public class MetadataImport { String cHandle = c.getHandle(); String cName = c.getName(); if (!changed) { - System.out.print(" + Un-map from collection (" + cHandle + "): "); + handler.logInfo(" + Un-map from collection (" + cHandle + "): "); } else { - System.out.print(" + Un-mapped from collection (" + cHandle + "): "); + handler.logInfo(" + Un-mapped from collection (" + cHandle + "): "); } - System.out.println(cName); + handler.logInfo(cName); } // Show additions @@ -1194,16 +1328,15 @@ public class MetadataImport { md += "[" + metadataValue.getLanguage() + "]"; } if (!changed) { - System.out.print(" + Add (" + md + "): "); + handler.logInfo(" + Add (" + md + "): "); } else { - System.out.print(" + Added (" + md + "): "); + handler.logInfo(" + Added (" + md + "): "); } - System.out.print(metadataValue.getValue()); + handler.logInfo(metadataValue.getValue()); if (isAuthorityControlledField(md)) { - System.out.print(", authority = " + metadataValue.getAuthority()); - System.out.print(", confidence = " + metadataValue.getConfidence()); + handler.logInfo(", authority = " + metadataValue.getAuthority()); + handler.logInfo(", confidence = " + metadataValue.getConfidence()); } - System.out.println(""); } // Show removals @@ -1216,16 +1349,15 @@ public class MetadataImport { md += "[" + metadataValue.getLanguage() + "]"; } if (!changed) { - System.out.print(" - Remove (" + md + "): "); + handler.logInfo(" - Remove (" + md + "): "); } else { - System.out.print(" - Removed (" + md + "): "); + handler.logInfo(" - Removed (" + md + "): "); } - System.out.print(metadataValue.getValue()); + handler.logInfo(metadataValue.getValue()); if (isAuthorityControlledField(md)) { - System.out.print(", authority = " + metadataValue.getAuthority()); - System.out.print(", confidence = " + metadataValue.getConfidence()); + handler.logInfo(", authority = " + metadataValue.getAuthority()); + handler.logInfo(", confidence = " + metadataValue.getConfidence()); } - System.out.println(""); } } return changeCounter; @@ -1243,7 +1375,7 @@ public class MetadataImport { /** * Set authority controlled fields */ - private static void setAuthorizedMetadataFields() { + private void setAuthorizedMetadataFields() { authorityControlled = new HashSet(); Enumeration propertyNames = ConfigurationManager.getProperties().propertyNames(); while (propertyNames.hasMoreElements()) { @@ -1255,191 +1387,6 @@ public class MetadataImport { } } - /** - * main method to run the metadata exporter - * - * @param argv the command line arguments given - */ - public static void main(String[] argv) { - // Create an options object and populate it - CommandLineParser parser = new PosixParser(); - - Options options = new Options(); - - options.addOption("f", "file", true, "source file"); - options.addOption("e", "email", true, "email address or user id of user (required if adding new items)"); - options.addOption("s", "silent", false, - "silent operation - doesn't request confirmation of changes USE WITH CAUTION"); - options.addOption("w", "workflow", false, "workflow - when adding new items, use collection workflow"); - options.addOption("n", "notify", false, - "notify - when adding new items using a workflow, send notification emails"); - options.addOption("t", "template", false, - "template - when adding new items, use the collection template (if it exists)"); - options.addOption("v", "validate-only", false, - "validate - just validate the csv, don't run the import"); - options.addOption("h", "help", false, "help"); - - // Parse the command line arguments - CommandLine line; - try { - line = parser.parse(options, argv); - } catch (ParseException pe) { - System.err.println("Error parsing command line arguments: " + pe.getMessage()); - System.exit(1); - return; - } - - if (line.hasOption('h')) { - printHelp(options, 0); - } - - // Check a filename is given - if (!line.hasOption('f')) { - System.err.println("Required parameter -f missing!"); - printHelp(options, 1); - } - String filename = line.getOptionValue('f'); - - // Option to apply template to new items - boolean useTemplate = false; - if (line.hasOption('t')) { - useTemplate = true; - } - - // Options for workflows, and workflow notifications for new items - boolean useWorkflow = false; - boolean workflowNotify = false; - if (line.hasOption('w')) { - useWorkflow = true; - if (line.hasOption('n')) { - workflowNotify = true; - } - } else if (line.hasOption('n')) { - System.err.println("Invalid option 'n': (notify) can only be specified with the 'w' (workflow) option."); - System.exit(1); - } - - // Create a context - Context c; - try { - c = new Context(); - c.turnOffAuthorisationSystem(); - } catch (Exception e) { - System.err.println("Unable to create a new DSpace Context: " + e.getMessage()); - System.exit(1); - return; - } - - // Find the EPerson, assign to context - try { - if (line.hasOption('e')) { - EPerson eperson; - String e = line.getOptionValue('e'); - if (e.indexOf('@') != -1) { - eperson = EPersonServiceFactory.getInstance().getEPersonService().findByEmail(c, e); - } else { - eperson = EPersonServiceFactory.getInstance().getEPersonService().find(c, UUID.fromString(e)); - } - - if (eperson == null) { - System.out.println("Error, eperson cannot be found: " + e); - System.exit(1); - } - c.setCurrentUser(eperson); - } - } catch (Exception e) { - System.err.println("Unable to find DSpace user: " + e.getMessage()); - System.exit(1); - return; - } - - // Is this a silent run? - boolean change = false; - - // Read lines from the CSV file - DSpaceCSV csv; - try { - csv = new DSpaceCSV(new File(filename), c); - } catch (MetadataImportInvalidHeadingException miihe) { - System.err.println(miihe.getMessage()); - System.exit(1); - return; - } catch (Exception e) { - System.err.println("Error reading file: " + e.getMessage()); - System.exit(1); - return; - } - - // Perform the first import - just highlight differences - MetadataImport importer = new MetadataImport(c, csv); - List changes; - - boolean validateOnly = line.hasOption('v'); - - if (!line.hasOption('s') || validateOnly) { - // See what has changed - try { - changes = importer.runImport(false, useWorkflow, workflowNotify, useTemplate); - } catch (MetadataImportException mie) { - System.err.println("Error: " + mie.getMessage()); - System.exit(1); - return; - } - - // Display the changes - int changeCounter = displayChanges(changes, false); - - // If there were changes, ask if we should execute them - if (!validateOnly && changeCounter > 0) { - try { - // Ask the user if they want to make the changes - System.out.println("\n" + changeCounter + " item(s) will be changed\n"); - System.out.print("Do you want to make these changes? [y/n] "); - String yn = (new BufferedReader(new InputStreamReader(System.in))).readLine(); - if ("y".equalsIgnoreCase(yn)) { - change = true; - } else { - System.out.println("No data has been changed."); - } - } catch (IOException ioe) { - System.err.println("Error: " + ioe.getMessage()); - System.err.println("No changes have been made"); - System.exit(1); - } - } else { - System.out.println("There were no changes detected"); - } - } else { - change = true; - } - - try { - // If required, make the change - if (change && !validateOnly) { - try { - // Make the changes - changes = importer.runImport(true, useWorkflow, workflowNotify, useTemplate); - } catch (MetadataImportException mie) { - System.err.println("Error: " + mie.getMessage()); - System.exit(1); - return; - } - - // Display the changes - displayChanges(changes, true); - } - - // Finsh off and tidy up - c.restoreAuthSystemState(); - c.complete(); - } catch (Exception e) { - c.abort(); - System.err.println("Error committing changes to database: " + e.getMessage()); - System.err.println("Aborting most recent changes."); - System.exit(1); - } - } - /** * Gets a copy of the given csv line with all entity target references resolved to UUID strings. * Keys being iterated over represent metadatafields or special columns to be processed. @@ -1448,7 +1395,7 @@ public class MetadataImport { * @return a copy, with all references resolved. * @throws MetadataImportException if there is an error resolving any entity target reference. */ - public DSpaceCSVLine resolveEntityRefs(DSpaceCSVLine line) throws MetadataImportException { + public DSpaceCSVLine resolveEntityRefs(Context c, DSpaceCSVLine line) throws MetadataImportException { DSpaceCSVLine newLine = new DSpaceCSVLine(line.getID()); UUID originId = evaluateOriginId(line.getID()); for (String key : line.keys()) { @@ -1503,7 +1450,7 @@ public class MetadataImport { originIds.add(originId); typeNames.put(relationField, originIds); } else { - ArrayList originIds = typeNames.get(relationField); + ArrayList originIds = typeNames.get(relationField); originIds.add(originId); typeNames.put(relationField, originIds); } @@ -1533,7 +1480,7 @@ public class MetadataImport { } for (String key : line.keys()) { if (key.contains(".") && !key.split("\\.")[0].equalsIgnoreCase("relation") || - key.equalsIgnoreCase("rowName")) { + key.equalsIgnoreCase("rowName")) { for (String value : line.get(key)) { String valueKey = key + ":" + value; Set rowNums = csvRefMap.get(valueKey); @@ -1575,20 +1522,20 @@ public class MetadataImport { try { return UUID.fromString(reference); } catch (IllegalArgumentException e) { - throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + - "Not a UUID or indirect entity reference: '" + reference + "'"); + throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + + "Not a UUID or indirect entity reference: '" + reference + "'"); } - } else if (!reference.startsWith("rowName:") ) { // Not a rowName ref; so it's a metadata value reference + } else if (!reference.startsWith("rowName:")) { // Not a rowName ref; so it's a metadata value reference MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService(); MetadataFieldService metadataFieldService = - ContentServiceFactory.getInstance().getMetadataFieldService(); + ContentServiceFactory.getInstance().getMetadataFieldService(); int i = reference.indexOf(":"); String mfValue = reference.substring(i + 1); String mf[] = reference.substring(0, i).split("\\."); if (mf.length < 2) { - throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + - "Bad metadata field in reference: '" + reference - + "' (expected syntax is schema.element[.qualifier])"); + throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + + "Bad metadata field in reference: '" + reference + + "' (expected syntax is schema.element[.qualifier])"); } String schema = mf[0]; String element = mf[1]; @@ -1600,13 +1547,13 @@ public class MetadataImport { MetadataValue mdvVal = mdv.next(); uuid = mdvVal.getDSpaceObject().getID(); if (mdv.hasNext()) { - throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + - "Ambiguous reference; multiple matches in db: " + reference); + throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + + "Ambiguous reference; multiple matches in db: " + reference); } } } catch (SQLException e) { - throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + - "Error looking up item by metadata reference: " + reference, e); + throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + + "Error looking up item by metadata reference: " + reference, e); } } // Lookup UUIDs that may have already been processed into the csvRefMap @@ -1614,24 +1561,25 @@ public class MetadataImport { // See getMatchingCSVUUIDs() for how the reference param is sourced from the csvRefMap Set csvUUIDs = getMatchingCSVUUIDs(reference); if (csvUUIDs.size() > 1) { - throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + - "Ambiguous reference; multiple matches in csv: " + reference); + throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + + "Ambiguous reference; multiple matches in csv: " + reference); } else if (csvUUIDs.size() == 1) { UUID csvUUID = csvUUIDs.iterator().next(); if (csvUUID.equals(uuid)) { return uuid; // one match from csv and db (same item) } else if (uuid != null) { - throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + - "Ambiguous reference; multiple matches in db and csv: " + reference); + throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + + "Ambiguous reference; multiple matches in db and csv: " + reference); } else { return csvUUID; // one match from csv } } else { // size == 0; the reference does not exist throw an error if (uuid == null) { - throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + - "No matches found for reference: " + reference - + "\nKeep in mind you can only reference entries that are listed before " + - "this one within the CSV."); + throw new MetadataImportException("Error in CSV row " + rowCount + ":\n" + + "No matches found for reference: " + reference + + "\nKeep in mind you can only reference entries that are " + + "listed before " + + "this one within the CSV."); } else { return uuid; // one match from db } @@ -1688,14 +1636,16 @@ public class MetadataImport { * Validate every relation modification expressed in the CSV. * */ - private void validateExpressedRelations() throws MetadataImportException { + private void validateExpressedRelations(Context c) throws MetadataImportException { for (String targetUUID : entityRelationMap.keySet()) { String targetType = null; try { // Get the type of reference. Attempt lookup in processed map first before looking in archive. if (entityTypeMap.get(UUID.fromString(targetUUID)) != null) { targetType = entityTypeService. - findByEntityType(c, entityTypeMap.get(UUID.fromString(targetUUID))).getLabel(); + findByEntityType(c, + entityTypeMap.get(UUID.fromString(targetUUID))) + .getLabel(); } else { // Target item may be archived; check there. // Add to errors if Realtionship.type cannot be derived @@ -1703,18 +1653,19 @@ public class MetadataImport { if (itemService.find(c, UUID.fromString(targetUUID)) != null) { targetItem = itemService.find(c, UUID.fromString(targetUUID)); List relTypes = itemService. - getMetadata(targetItem, "relationship", "type", null, Item.ANY); + getMetadata(targetItem, "relationship", "type", + null, Item.ANY); String relTypeValue = null; if (relTypes.size() > 0) { relTypeValue = relTypes.get(0).getValue(); targetType = entityTypeService.findByEntityType(c, relTypeValue).getLabel(); } else { relationValidationErrors.add("Cannot resolve Entity type for target UUID: " + - targetUUID); + targetUUID); } } else { relationValidationErrors.add("Cannot resolve Entity type for target UUID: " + - targetUUID); + targetUUID); } } if (targetType == null) { @@ -1739,7 +1690,7 @@ public class MetadataImport { // Attempt lookup in processed map first before looking in archive. if (entityTypeMap.get(UUID.fromString(originRefererUUID)) != null) { originType = entityTypeMap.get(UUID.fromString(originRefererUUID)); - validateTypesByTypeByTypeName(targetType, originType, typeName, originRow); + validateTypesByTypeByTypeName(c, targetType, originType, typeName, originRow); } else { // Origin item may be archived; check there. // Add to errors if Realtionship.type cannot be derived. @@ -1747,22 +1698,23 @@ public class MetadataImport { if (itemService.find(c, UUID.fromString(targetUUID)) != null) { originItem = itemService.find(c, UUID.fromString(originRefererUUID)); List relTypes = itemService. - getMetadata(originItem, "relationship", "type", null, Item.ANY); + getMetadata(originItem, "relationship", + "type", null, Item.ANY); String relTypeValue = null; if (relTypes.size() > 0) { relTypeValue = relTypes.get(0).getValue(); originType = entityTypeService.findByEntityType(c, relTypeValue).getLabel(); - validateTypesByTypeByTypeName(targetType, originType, typeName, originRow); + validateTypesByTypeByTypeName(c, targetType, originType, typeName, originRow); } else { relationValidationErrors.add("Error on CSV row " + originRow + ":" + "\n" + - "Cannot resolve Entity type for reference: " - + originRefererUUID); + "Cannot resolve Entity type for reference: " + + originRefererUUID); } } else { relationValidationErrors.add("Error on CSV row " + originRow + ":" + "\n" + - "Cannot resolve Entity type for reference: " - + originRefererUUID + " in row: " + originRow ); + "Cannot resolve Entity type for reference: " + + originRefererUUID + " in row: " + originRow); } } } @@ -1791,20 +1743,22 @@ public class MetadataImport { * @param typeName left or right typeName of the respective Relationship. * @return the UUID of the item. */ - private void validateTypesByTypeByTypeName(String targetType, String originType, String typeName, String originRow) - throws MetadataImportException { + private void validateTypesByTypeByTypeName(Context c, + String targetType, String originType, String typeName, String originRow) + throws MetadataImportException { try { RelationshipType foundRelationshipType = null; List relationshipTypeList = relationshipTypeService. - findByLeftwardOrRightwardTypeName(c, typeName.split("\\.")[1]); + findByLeftwardOrRightwardTypeName( + c, typeName.split("\\.")[1]); // Validate described relationship form the CSV. foundRelationshipType = matchRelationshipType(relationshipTypeList, targetType, originType, typeName); if (foundRelationshipType == null) { relationValidationErrors.add("Error on CSV row " + originRow + ":" + "\n" + - "No Relationship type found for:\n" + - "Target type: " + targetType + "\n" + - "Origin referer type: " + originType + "\n" + - "with typeName: " + typeName + " for type: " + originType); + "No Relationship type found for:\n" + + "Target type: " + targetType + "\n" + + "Origin referer type: " + originType + "\n" + + "with typeName: " + typeName + " for type: " + originType); } } catch (SQLException sqle) { throw new MetadataImportException("Error interacting with database!", sqle); @@ -1837,7 +1791,7 @@ public class MetadataImport { continue; } if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(originType) && - relationshipType.getRightType().getLabel().equalsIgnoreCase(targetType)) { + relationshipType.getRightType().getLabel().equalsIgnoreCase(targetType)) { foundRelationshipType = relationshipType; } } else { @@ -1845,7 +1799,7 @@ public class MetadataImport { continue; } if (relationshipType.getLeftType().getLabel().equalsIgnoreCase(targetType) && - relationshipType.getRightType().getLabel().equalsIgnoreCase(originType)) { + relationshipType.getRightType().getLabel().equalsIgnoreCase(originType)) { foundRelationshipType = relationshipType; } } @@ -1853,4 +1807,4 @@ public class MetadataImport { return foundRelationshipType; } -} +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportCLI.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportCLI.java new file mode 100644 index 0000000000..efc396d68f --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportCLI.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.bulkedit; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +import org.dspace.scripts.handler.DSpaceRunnableHandler; + +/** + * CLI variant for the {@link MetadataImport} class + * This has been made so that we can specify the behaviour of the determineChanges method to be specific for the CLI + */ +public class MetadataImportCLI extends MetadataImport { + + @Override + protected boolean determineChange(DSpaceRunnableHandler handler) throws IOException { + handler.logInfo("Do you want to make these changes? [y/n] "); + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in))) { + String yn = bufferedReader.readLine(); + if ("y".equalsIgnoreCase(yn)) { + return true; + } + return false; + } + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportCliScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportCliScriptConfiguration.java new file mode 100644 index 0000000000..3504eddac3 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportCliScriptConfiguration.java @@ -0,0 +1,16 @@ +/** + * 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.bulkedit; + +import org.dspace.scripts.configuration.ScriptConfiguration; + +/** + * The {@link ScriptConfiguration} for the {@link org.dspace.app.bulkedit.MetadataImportCLI} CLI script + */ +public class MetadataImportCliScriptConfiguration extends MetadataImportScriptConfiguration { +} diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportScriptConfiguration.java new file mode 100644 index 0000000000..9ea50b7de5 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportScriptConfiguration.java @@ -0,0 +1,84 @@ +/** + * 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.bulkedit; + +import java.io.InputStream; +import java.sql.SQLException; + +import org.apache.commons.cli.Options; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.core.Context; +import org.dspace.scripts.configuration.ScriptConfiguration; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * The {@link ScriptConfiguration} for the {@link MetadataImport} script + */ +public class MetadataImportScriptConfiguration extends ScriptConfiguration { + + @Autowired + private AuthorizeService authorizeService; + + private Class dspaceRunnableClass; + + @Override + public Class getDspaceRunnableClass() { + return dspaceRunnableClass; + } + + /** + * Generic setter for the dspaceRunnableClass + * @param dspaceRunnableClass The dspaceRunnableClass to be set on this MetadataImportScriptConfiguration + */ + @Override + public void setDspaceRunnableClass(Class dspaceRunnableClass) { + this.dspaceRunnableClass = dspaceRunnableClass; + } + + @Override + public boolean isAllowedToExecute(Context context) { + try { + return authorizeService.isAdmin(context); + } catch (SQLException e) { + throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); + } + } + + @Override + public Options getOptions() { + if (options == null) { + Options options = new Options(); + + options.addOption("f", "file", true, "source file"); + options.getOption("f").setType(InputStream.class); + options.getOption("f").setRequired(true); + options.addOption("e", "email", true, "email address or user id of user (required if adding new items)"); + options.getOption("e").setType(String.class); + options.getOption("e").setRequired(true); + options.addOption("s", "silent", false, + "silent operation - doesn't request confirmation of changes USE WITH CAUTION"); + options.getOption("s").setType(boolean.class); + options.addOption("w", "workflow", false, "workflow - when adding new items, use collection workflow"); + options.getOption("w").setType(boolean.class); + options.addOption("n", "notify", false, + "notify - when adding new items using a workflow, send notification emails"); + options.getOption("n").setType(boolean.class); + options.addOption("v", "validate-only", false, + "validate - just validate the csv, don't run the import"); + options.getOption("v").setType(boolean.class); + options.addOption("t", "template", false, + "template - when adding new items, use the collection template (if it exists)"); + options.getOption("t").setType(boolean.class); + options.addOption("h", "help", false, "help"); + options.getOption("h").setType(boolean.class); + + super.options = options; + } + return options; + } +} 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 ef6b0b538e..6ee62bd904 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 @@ -16,9 +16,11 @@ import java.util.TreeMap; import org.apache.commons.cli.ParseException; import org.apache.log4j.Logger; import org.dspace.scripts.DSpaceRunnable; +import org.dspace.scripts.configuration.ScriptConfiguration; import org.dspace.scripts.factory.ScriptServiceFactory; import org.dspace.scripts.handler.DSpaceRunnableHandler; import org.dspace.scripts.handler.impl.CommandLineDSpaceRunnableHandler; +import org.dspace.scripts.service.ScriptService; import org.dspace.servicemanager.DSpaceKernelImpl; import org.dspace.servicemanager.DSpaceKernelInit; import org.dspace.services.RequestService; @@ -44,7 +46,8 @@ public class ScriptLauncher { /** * Default constructor */ - private ScriptLauncher() { } + private ScriptLauncher() { + } /** * Execute the DSpace script launcher @@ -54,7 +57,7 @@ public class ScriptLauncher { * @throws FileNotFoundException if file doesn't exist */ public static void main(String[] args) - throws FileNotFoundException, IOException { + throws FileNotFoundException, IOException, IllegalAccessException, InstantiationException { // Initialise the service manager kernel try { kernelImpl = DSpaceKernelInit.getKernel(null); @@ -107,13 +110,18 @@ public class ScriptLauncher { * @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 + * @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) { + DSpaceRunnableHandler dSpaceRunnableHandler, + DSpaceKernelImpl kernelImpl) throws InstantiationException, IllegalAccessException { int status; - DSpaceRunnable script = ScriptServiceFactory.getInstance().getScriptService().getScriptForName(args[0]); + ScriptService scriptService = ScriptServiceFactory.getInstance().getScriptService(); + ScriptConfiguration scriptConfiguration = scriptService.getScriptConfiguration(args[0]); + DSpaceRunnable script = null; + if (scriptConfiguration != null) { + script = scriptService.createDSpaceRunnableForScriptConfiguration(scriptConfiguration); + } if (script != null) { status = executeScript(args, dSpaceRunnableHandler, script); } else { @@ -127,12 +135,12 @@ public class ScriptLauncher { * @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 + * @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.initialize(args, dSpaceRunnableHandler, null); script.run(); return 0; } catch (ParseException e) { diff --git a/dspace-api/src/main/java/org/dspace/app/util/AuthorizeUtil.java b/dspace-api/src/main/java/org/dspace/app/util/AuthorizeUtil.java index 39fb713970..ea1fb87ff4 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/AuthorizeUtil.java +++ b/dspace-api/src/main/java/org/dspace/app/util/AuthorizeUtil.java @@ -590,8 +590,11 @@ public class AuthorizeUtil { authorizeManageAdminGroup(context, collection); return; } - - + // if we reach this point, it means that the group is related + // to a collection but as it is not the submitters, nor the administrators, + // nor a workflow groups it must be a default item/bitstream groups + authorizeManageDefaultReadGroup(context, collection); + return; } if (parentObject.getType() == Constants.COMMUNITY) { Community community = (Community) parentObject; @@ -601,4 +604,38 @@ public class AuthorizeUtil { throw new AuthorizeException("not authorized to manage this group"); } + + /** + * This method checks if the community Admin can manage accounts + * + * @return true if is able + */ + public static boolean canCommunityAdminManageAccounts() { + boolean isAble = false; + if (AuthorizeConfiguration.canCommunityAdminManagePolicies() + || AuthorizeConfiguration.canCommunityAdminManageAdminGroup() + || AuthorizeConfiguration.canCommunityAdminManageCollectionPolicies() + || AuthorizeConfiguration.canCommunityAdminManageCollectionSubmitters() + || AuthorizeConfiguration.canCommunityAdminManageCollectionWorkflows() + || AuthorizeConfiguration.canCommunityAdminManageCollectionAdminGroup()) { + isAble = true; + } + return isAble; + } + + /** + * This method checks if the Collection Admin can manage accounts + * + * @return true if is able + */ + public static boolean canCollectionAdminManageAccounts() { + boolean isAble = false; + if (AuthorizeConfiguration.canCollectionAdminManagePolicies() + || AuthorizeConfiguration.canCollectionAdminManageSubmitters() + || AuthorizeConfiguration.canCollectionAdminManageWorkflows() + || AuthorizeConfiguration.canCollectionAdminManageAdminGroup()) { + isAble = true; + } + return isAble; + } } diff --git a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java index 2384a260da..07a7ac70d3 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/authorize/AuthorizeServiceImpl.java @@ -430,7 +430,11 @@ public class AuthorizeServiceImpl implements AuthorizeService { public boolean isCommunityAdmin(Context c) throws SQLException { EPerson e = c.getCurrentUser(); + return isCommunityAdmin(c, e); + } + @Override + public boolean isCommunityAdmin(Context c, EPerson e) throws SQLException { if (e != null) { List policies = resourcePolicyService.find(c, e, groupService.allMemberGroups(c, e), @@ -446,7 +450,11 @@ public class AuthorizeServiceImpl implements AuthorizeService { public boolean isCollectionAdmin(Context c) throws SQLException { EPerson e = c.getCurrentUser(); + return isCollectionAdmin(c, e); + } + @Override + public boolean isCollectionAdmin(Context c, EPerson e) throws SQLException { if (e != null) { List policies = resourcePolicyService.find(c, e, groupService.allMemberGroups(c, e), diff --git a/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java b/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java index 9e739e2585..f3ede72ac1 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java +++ b/dspace-api/src/main/java/org/dspace/authorize/service/AuthorizeService.java @@ -213,6 +213,26 @@ public interface AuthorizeService { public boolean isCollectionAdmin(Context c) throws SQLException; + /** + * Check to see if a specific user is Community admin + * + * @param c current context + * @param e the user to check + * @return true if user is an admin of some community + * @throws SQLException + */ + public boolean isCommunityAdmin(Context c, EPerson e) throws SQLException; + + /** + * Check to see if a specific user is Collection admin + * + * @param c current context + * @param e the user to check + * @return true if user is an admin of some collection + * @throws SQLException if database error + */ + public boolean isCollectionAdmin(Context c, EPerson e) throws SQLException; + /////////////////////////////////////////////// // policy manipulation methods /////////////////////////////////////////////// diff --git a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java index c4f8d288b3..34bf4f5fc1 100644 --- a/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/CollectionServiceImpl.java @@ -17,11 +17,13 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.MissingResourceException; +import java.util.Set; import java.util.UUID; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; +import org.apache.solr.client.solrj.util.ClientUtils; import org.dspace.app.util.AuthorizeUtil; import org.dspace.authorize.AuthorizeConfiguration; import org.dspace.authorize.AuthorizeException; @@ -40,6 +42,13 @@ import org.dspace.core.Context; import org.dspace.core.I18nUtil; import org.dspace.core.LogManager; import org.dspace.core.service.LicenseService; +import org.dspace.discovery.DiscoverQuery; +import org.dspace.discovery.DiscoverResult; +import org.dspace.discovery.IndexableObject; +import org.dspace.discovery.SearchService; +import org.dspace.discovery.SearchServiceException; +import org.dspace.discovery.indexobject.IndexableCollection; +import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.service.GroupService; import org.dspace.eperson.service.SubscribeService; @@ -100,6 +109,9 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i @Autowired(required = true) protected CollectionRoleService collectionRoleService; + @Autowired(required = true) + protected SearchService searchService; + protected CollectionServiceImpl() { super(); } @@ -907,4 +919,77 @@ public class CollectionServiceImpl extends DSpaceObjectServiceImpl i return role; } + @Override + public List findCollectionsWithSubmit(String q, Context context, Community community, + int offset, int limit) throws SQLException, SearchServiceException { + + List collections = new ArrayList(); + DiscoverQuery discoverQuery = new DiscoverQuery(); + discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); + discoverQuery.setStart(offset); + discoverQuery.setMaxResults(limit); + DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery,community, q); + for (IndexableObject solrCollections : resp.getIndexableObjects()) { + Collection c = ((IndexableCollection) solrCollections).getIndexedObject(); + collections.add(c); + } + return collections; + } + + @Override + public int countCollectionsWithSubmit(String q, Context context, Community community) + throws SQLException, SearchServiceException { + + DiscoverQuery discoverQuery = new DiscoverQuery(); + discoverQuery.setMaxResults(0); + discoverQuery.setDSpaceObjectFilter(IndexableCollection.TYPE); + DiscoverResult resp = retrieveCollectionsWithSubmit(context, discoverQuery,community,q); + return (int)resp.getTotalSearchResults(); + } + + /** + * Finds all Indexed Collections where the current user has submit rights. If the user is an Admin, + * this is all Indexed Collections. Otherwise, it includes those collections where + * an indexed "submit" policy lists either the eperson or one of the eperson's groups + * + * @param context DSpace context + * @param discoverQuery + * @param community parent community, could be null + * @param q limit the returned collection to those with metadata values matching the query + * terms. The terms are used to make also a prefix query on SOLR + * so it can be used to implement an autosuggest feature over the collection name + * @return discovery search result objects + * @throws SQLException if something goes wrong + * @throws SearchServiceException if search error + */ + private DiscoverResult retrieveCollectionsWithSubmit(Context context, DiscoverQuery discoverQuery, + Community community, String q) throws SQLException, SearchServiceException { + + StringBuilder query = new StringBuilder(); + EPerson currentUser = context.getCurrentUser(); + if (!authorizeService.isAdmin(context)) { + String userId = ""; + if (currentUser != null) { + userId = currentUser.getID().toString(); + } + query.append("submit:(e").append(userId); + Set groups = groupService.allMemberGroupsSet(context, currentUser); + for (Group group : groups) { + query.append(" OR g").append(group.getID()); + } + query.append(")"); + discoverQuery.addFilterQueries(query.toString()); + } + if (community != null) { + discoverQuery.addFilterQueries("location.comm:" + community.getID().toString()); + } + if (StringUtils.isNotBlank(q)) { + StringBuilder buildQuery = new StringBuilder(); + String escapedQuery = ClientUtils.escapeQueryChars(q); + buildQuery.append(escapedQuery).append(" OR ").append(escapedQuery).append("*"); + discoverQuery.setQuery(buildQuery.toString()); + } + DiscoverResult resp = searchService.search(context, discoverQuery); + return resp; + } } diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java new file mode 100644 index 0000000000..85cfcba2e0 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/MetadataDSpaceCsvExportServiceImpl.java @@ -0,0 +1,129 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import com.google.common.collect.Iterators; +import org.dspace.app.bulkedit.DSpaceCSV; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.MetadataDSpaceCsvExportService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.handle.factory.HandleServiceFactory; +import org.dspace.scripts.handler.DSpaceRunnableHandler; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Implementation of {@link MetadataDSpaceCsvExportService} + */ +public class MetadataDSpaceCsvExportServiceImpl implements MetadataDSpaceCsvExportService { + + @Autowired + private ItemService itemService; + + @Override + public DSpaceCSV handleExport(Context context, boolean exportAllItems, boolean exportAllMetadata, String handle, + DSpaceRunnableHandler handler) throws Exception { + Iterator toExport = null; + + if (exportAllItems) { + handler.logInfo("Exporting whole repository WARNING: May take some time!"); + toExport = itemService.findAll(context); + } else { + DSpaceObject dso = HandleServiceFactory.getInstance().getHandleService().resolveToObject(context, handle); + if (dso == null) { + throw new IllegalArgumentException( + "Item '" + handle + "' does not resolve to an item in your repository!"); + } + + if (dso.getType() == Constants.ITEM) { + handler.logInfo("Exporting item '" + dso.getName() + "' (" + handle + ")"); + List item = new ArrayList<>(); + item.add((Item) dso); + toExport = item.iterator(); + } else if (dso.getType() == Constants.COLLECTION) { + handler.logInfo("Exporting collection '" + dso.getName() + "' (" + handle + ")"); + Collection collection = (Collection) dso; + toExport = itemService.findByCollection(context, collection); + } else if (dso.getType() == Constants.COMMUNITY) { + handler.logInfo("Exporting community '" + dso.getName() + "' (" + handle + ")"); + toExport = buildFromCommunity(context, (Community) dso); + } else { + throw new IllegalArgumentException("Error identifying '" + handle + "'"); + } + } + + DSpaceCSV csv = this.export(context, toExport, exportAllMetadata); + return csv; + } + + @Override + public DSpaceCSV export(Context context, Iterator toExport, boolean exportAll) throws Exception { + Context.Mode originalMode = context.getCurrentMode(); + context.setMode(Context.Mode.READ_ONLY); + + // Process each item + DSpaceCSV csv = new DSpaceCSV(exportAll); + while (toExport.hasNext()) { + Item item = toExport.next(); + csv.addItem(item); + context.uncacheEntity(item); + } + + context.setMode(originalMode); + // Return the results + return csv; + } + + @Override + public DSpaceCSV export(Context context, Community community, boolean exportAll) throws Exception { + return export(context, buildFromCommunity(context, community), exportAll); + } + + /** + * Build an array list of item ids that are in a community (include sub-communities and collections) + * + * @param context DSpace context + * @param community The community to build from + * @return The list of item ids + * @throws SQLException if database error + */ + private Iterator buildFromCommunity(Context context, Community community) + throws SQLException { + // Add all the collections + List collections = community.getCollections(); + Iterator result = null; + for (Collection collection : collections) { + Iterator items = itemService.findByCollection(context, collection); + result = addItemsToResult(result, items); + + } + // Add all the sub-communities + List communities = community.getSubcommunities(); + for (Community subCommunity : communities) { + Iterator items = buildFromCommunity(context, subCommunity); + result = addItemsToResult(result, items); + } + + return result; + } + + private Iterator addItemsToResult(Iterator result, Iterator items) { + if (result == null) { + result = items; + } else { + result = Iterators.concat(result, items); + } + + return result; + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java index 5038aef6d7..8637b61703 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/CollectionService.java @@ -20,8 +20,10 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.core.Context; +import org.dspace.discovery.SearchServiceException; import org.dspace.eperson.Group; + /** * Service interface class for the Collection object. * The implementation of this class is responsible for all business logic calls for the Collection object and is @@ -354,4 +356,42 @@ public interface CollectionService */ Group createDefaultReadGroup(Context context, Collection collection, String typeOfGroupString, int defaultRead) throws SQLException, AuthorizeException; + + /** + * Returns Collections for which the current user has 'submit' privileges. + * NOTE: for better performance, this method retrieves its results from an + * index (cache) and does not query the database directly. + * This means that results may be stale or outdated until DS-4524 is resolved" + * + * @param q limit the returned collection to those with metadata values matching the query terms. + * The terms are used to make also a prefix query on SOLR so it can be used to implement + * an autosuggest feature over the collection name + * @param context DSpace Context + * @param community parent community + * @param offset the position of the first result to return + * @param limit paging limit + * @return discovery search result objects + * @throws SQLException if something goes wrong + * @throws SearchServiceException if search error + */ + public List findCollectionsWithSubmit(String q, Context context, Community community, + int offset, int limit) throws SQLException, SearchServiceException; + + /** + * Counts the number of Collection for which the current user has 'submit' privileges. + * NOTE: for better performance, this method retrieves its results from an index (cache) + * and does not query the database directly. + * This means that results may be stale or outdated until DS-4524 is resolved." + * + * @param q limit the returned collection to those with metadata values matching the query terms. + * The terms are used to make also a prefix query on SOLR so it can be used to implement + * an autosuggest feature over the collection name + * @param context DSpace Context + * @param community parent community + * @return total collections found + * @throws SQLException if something goes wrong + * @throws SearchServiceException if search error + */ + public int countCollectionsWithSubmit(String q, Context context, Community community) + throws SQLException, SearchServiceException; } diff --git a/dspace-api/src/main/java/org/dspace/content/service/MetadataDSpaceCsvExportService.java b/dspace-api/src/main/java/org/dspace/content/service/MetadataDSpaceCsvExportService.java new file mode 100644 index 0000000000..aeb956fc49 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/service/MetadataDSpaceCsvExportService.java @@ -0,0 +1,58 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.content.service; + +import java.util.Iterator; + +import org.dspace.app.bulkedit.DSpaceCSV; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.scripts.handler.DSpaceRunnableHandler; + +/** + * This is the interface to be implemented by a Service that deals with the exporting of Metadata + */ +public interface MetadataDSpaceCsvExportService { + + /** + * This method will export DSpaceObject objects depending on the parameters it gets. It can export all the items + * in the repository, all the items in a community, all the items in a collection or a specific item. The latter + * three are specified by the handle parameter. The entire repository can be exported by defining the + * exportAllItems parameter as true + * @param context The relevant DSpace context + * @param exportAllItems A boolean indicating whether or not the entire repository should be exported + * @param exportAllMetadata Defines if all metadata should be exported or only the allowed ones + * @param handle The handle for the DSpaceObject to be exported, can be a Community, Collection or Item + * @return A DSpaceCSV object containing the exported information + * @throws Exception If something goes wrong + */ + public DSpaceCSV handleExport(Context context, boolean exportAllItems, boolean exportAllMetadata, + String handle, DSpaceRunnableHandler dSpaceRunnableHandler) throws Exception; + + /** + * This method will export all the Items in the given toExport iterator to a DSpaceCSV + * @param context The relevant DSpace context + * @param toExport The iterator containing the items to export + * @param exportAll Defines if all metadata should be exported or only the allowed ones + * @return A DSpaceCSV object containing the exported information + * @throws Exception If something goes wrong + */ + public DSpaceCSV export(Context context, Iterator toExport, boolean exportAll) throws Exception; + + /** + * This method will export all the Items within the given Community to a DSpaceCSV + * @param context The relevant DSpace context + * @param community The Community that contains the Items to be exported + * @param exportAll Defines if all metadata should be exported or only the allowed ones + * @return A DSpaceCSV object containing the exported information + * @throws Exception If something goes wrong + */ + public DSpaceCSV export(Context context, Community community, boolean exportAll) throws Exception; + +} \ No newline at end of file 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 2e7b00a617..4e6fa16177 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexClient.java @@ -14,7 +14,6 @@ import java.util.Optional; import java.util.UUID; import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.dspace.content.Collection; import org.dspace.content.Community; @@ -30,17 +29,18 @@ import org.dspace.discovery.indexobject.factory.IndexFactory; import org.dspace.discovery.indexobject.factory.IndexObjectFactoryFactory; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.scripts.DSpaceRunnable; -import org.springframework.beans.factory.annotation.Autowired; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.utils.DSpace; /** * Class used to reindex dspace communities/collections/items into discovery */ -public class IndexClient extends DSpaceRunnable { +public class IndexClient extends DSpaceRunnable { private Context context; - - @Autowired - private IndexingService indexer; + private IndexingService indexer = DSpaceServicesFactory.getInstance().getServiceManager() + .getServiceByName(IndexingService.class.getName(), + IndexingService.class); private IndexClientOptions indexClientOptions; @@ -144,6 +144,12 @@ public class IndexClient extends DSpaceRunnable { handler.logInfo("Done with indexing"); } + @Override + public IndexDiscoveryScriptConfiguration getScriptConfiguration() { + return new DSpace().getServiceManager().getServiceByName("index-discovery", + IndexDiscoveryScriptConfiguration.class); + } + public void setup() throws ParseException { try { context = new Context(Context.Mode.READ_ONLY); @@ -151,18 +157,8 @@ public class IndexClient extends DSpaceRunnable { } 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; - } - /** * Indexes the given object and all children, if applicable. * diff --git a/dspace-api/src/main/java/org/dspace/discovery/IndexDiscoveryScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/discovery/IndexDiscoveryScriptConfiguration.java new file mode 100644 index 0000000000..8bf3cf2aba --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/IndexDiscoveryScriptConfiguration.java @@ -0,0 +1,58 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.discovery; + +import java.sql.SQLException; + +import org.apache.commons.cli.Options; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.core.Context; +import org.dspace.scripts.configuration.ScriptConfiguration; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * The {@link ScriptConfiguration} for the {@link IndexClient} script + */ +public class IndexDiscoveryScriptConfiguration extends ScriptConfiguration { + + @Autowired + private AuthorizeService authorizeService; + + private Class dspaceRunnableClass; + + @Override + public Class getDspaceRunnableClass() { + return dspaceRunnableClass; + } + + @Override + public boolean isAllowedToExecute(Context context) { + try { + return authorizeService.isAdmin(context); + } catch (SQLException e) { + throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); + } + } + + @Override + public Options getOptions() { + if (options == null) { + super.options = IndexClientOptions.constructOptions(); + } + return options; + } + + /** + * Generic setter for the dspaceRunnableClass + * @param dspaceRunnableClass The dspaceRunnableClass to be set on this IndexDiscoveryScriptConfiguration + */ + @Override + public void setDspaceRunnableClass(Class dspaceRunnableClass) { + this.dspaceRunnableClass = dspaceRunnableClass; + } +} diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.java new file mode 100644 index 0000000000..ebcaab78af --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrServiceIndexCollectionSubmittersPlugin.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.discovery; + +import java.sql.SQLException; +import java.util.List; + +import org.apache.logging.log4j.Logger; +import org.apache.solr.common.SolrInputDocument; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.core.LogManager; +import org.dspace.discovery.indexobject.IndexableCollection; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * The purpose of this plugin is to index all ADD type resource policies related to collections. + * + * @author Mykhaylo Boychuk (at 4science.it) + */ +public class SolrServiceIndexCollectionSubmittersPlugin implements SolrServiceIndexPlugin { + + private static final Logger log = org.apache.logging.log4j.LogManager + .getLogger(SolrServiceIndexCollectionSubmittersPlugin.class); + + @Autowired(required = true) + protected AuthorizeService authorizeService; + + @Override + public void additionalIndex(Context context, IndexableObject idxObj, SolrInputDocument document) { + if (idxObj instanceof IndexableCollection) { + Collection col = ((IndexableCollection) idxObj).getIndexedObject(); + if (col != null) { + try { + String fieldValue = null; + Community parent = (Community) ContentServiceFactory.getInstance().getDSpaceObjectService(col) + .getParentObject(context, col); + while (parent != null) { + if (parent.getAdministrators() != null) { + fieldValue = "g" + parent.getAdministrators().getID(); + document.addField("submit", fieldValue); + } + parent = (Community) ContentServiceFactory.getInstance().getDSpaceObjectService(parent) + .getParentObject(context, parent); + } + List policies = authorizeService.getPoliciesActionFilter(context, col, + Constants.ADD); + for (ResourcePolicy resourcePolicy : policies) { + if (resourcePolicy.getGroup() != null) { + fieldValue = "g" + resourcePolicy.getGroup().getID(); + } else { + fieldValue = "e" + resourcePolicy.getEPerson().getID(); + + } + document.addField("submit", fieldValue); + context.uncacheEntity(resourcePolicy); + } + } catch (SQLException e) { + log.error(LogManager.getHeader(context, "Error while indexing resource policies", + "Collection: (id " + col.getID() + " type " + col.getName() + ")" )); + } + } + } + } + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java index 7c23216458..4437516315 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java @@ -23,7 +23,9 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.dspace.authorize.AuthorizeConfiguration; import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.service.AuthorizeService; +import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.content.Collection; import org.dspace.content.DSpaceObject; import org.dspace.content.DSpaceObjectServiceImpl; @@ -76,6 +78,8 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl implements @Autowired(required = true) protected AuthorizeService authorizeService; + @Autowired(required = true) + protected ResourcePolicyService resourcePolicyService; protected GroupServiceImpl() { super(); @@ -654,6 +658,23 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl implements return collectionService.getParentObject(context, collection); } } + } else { + if (AuthorizeConfiguration.canCollectionAdminManagePolicies() + || AuthorizeConfiguration.canCommunityAdminManagePolicies() + || AuthorizeConfiguration.canCommunityAdminManageCollectionWorkflows()) { + List groups = new ArrayList(); + groups.add(group); + List policies = resourcePolicyService.find(context, null, groups, + Constants.DEFAULT_ITEM_READ, Constants.COLLECTION); + if (policies.size() > 0) { + return policies.get(0).getdSpaceObject(); + } + policies = resourcePolicyService.find(context, null, groups, + Constants.DEFAULT_BITSTREAM_READ, Constants.COLLECTION); + if (policies.size() > 0) { + return policies.get(0).getdSpaceObject(); + } + } } } if (AuthorizeConfiguration.canCommunityAdminManageAdminGroup()) { diff --git a/dspace-api/src/main/java/org/dspace/license/CCLicense.java b/dspace-api/src/main/java/org/dspace/license/CCLicense.java index b015e3a9d3..d5d9fe14a2 100644 --- a/dspace-api/src/main/java/org/dspace/license/CCLicense.java +++ b/dspace-api/src/main/java/org/dspace/license/CCLicense.java @@ -8,6 +8,8 @@ package org.dspace.license; +import java.util.List; + /** * @author wbossons */ @@ -15,17 +17,17 @@ public class CCLicense { private String licenseName; private String licenseId; - private int order = 0; + private List ccLicenseFieldList; public CCLicense() { super(); } - public CCLicense(String licenseId, String licenseName, int order) { + public CCLicense(String licenseId, String licenseName, List ccLicenseFieldList) { super(); this.licenseId = licenseId; this.licenseName = licenseName; - this.order = order; + this.ccLicenseFieldList = ccLicenseFieldList; } public String getLicenseName() { @@ -44,13 +46,19 @@ public class CCLicense { this.licenseId = licenseId; } - public int getOrder() { - return this.order; + /** + * Gets the list of CC License Fields + * @return the list of CC License Fields + */ + public List getCcLicenseFieldList() { + return ccLicenseFieldList; } - public void setOrder(int order) { - this.order = order; + /** + * Sets the list of CC License Fields + * @param ccLicenseFieldList + */ + public void setCcLicenseFieldList(final List ccLicenseFieldList) { + this.ccLicenseFieldList = ccLicenseFieldList; } - - } diff --git a/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorService.java b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorService.java new file mode 100644 index 0000000000..0c061d2d64 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorService.java @@ -0,0 +1,60 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.license; + +import java.io.IOException; +import java.util.Map; + +import org.jdom.Document; + +/** + * Service interface class for the Creative commons license connector service. + * The implementation of this class is responsible for all the calls to the CC license API and parsing the response + * The service is autowired by spring + */ +public interface CCLicenseConnectorService { + + /** + * Retrieves the CC Licenses for the provided language from the CC License API + * + * @param language - the language to retrieve the licenses for + * @return a map of licenses with the id and the license for the provided language + */ + public Map retrieveLicenses(String language); + + /** + * Retrieve the CC License URI based on the provided license id, language and answers to the field questions from + * the CC License API + * + * @param licenseId - the ID of the license + * @param language - the language for which to retrieve the full answerMap + * @param answerMap - the answers to the different field questions + * @return the CC License URI + */ + public String retrieveRightsByQuestion(String licenseId, + String language, + Map answerMap); + + /** + * Retrieve the license RDF document based on the license URI + * + * @param licenseURI - The license URI for which to retrieve the license RDF document + * @return the license RDF document + * @throws IOException + */ + public Document retrieveLicenseRDFDoc(String licenseURI) throws IOException; + + /** + * Retrieve the license Name from the license document + * + * @param doc - The license document from which to retrieve the license name + * @return the license name + */ + public String retrieveLicenseName(final Document doc); + +} diff --git a/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java new file mode 100644 index 0000000000..a237a91984 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/license/CCLicenseConnectorServiceImpl.java @@ -0,0 +1,371 @@ +/** + * 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.license; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.text.MessageFormat; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; +import org.apache.logging.log4j.Logger; +import org.dspace.services.ConfigurationService; +import org.jaxen.JaxenException; +import org.jaxen.jdom.JDOMXPath; +import org.jdom.Attribute; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.input.SAXBuilder; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.xml.sax.InputSource; + +/** + * Implementation for the Creative commons license connector service. + * This class is responsible for all the calls to the CC license API and parsing the response + */ +public class CCLicenseConnectorServiceImpl implements CCLicenseConnectorService, InitializingBean { + + private Logger log = org.apache.logging.log4j.LogManager.getLogger(CCLicenseConnectorServiceImpl.class); + + private CloseableHttpClient client; + protected SAXBuilder parser = new SAXBuilder(); + + private String postArgument = "answers"; + private String postAnswerFormat = + " " + + "{1}" + + "" + + "{2}" + + "" + + ""; + + + @Autowired + private ConfigurationService configurationService; + + @Override + public void afterPropertiesSet() throws Exception { + HttpClientBuilder builder = HttpClientBuilder.create(); + + client = builder + .disableAutomaticRetries() + .setMaxConnTotal(5) + .build(); + } + + /** + * Retrieves the CC Licenses for the provided language from the CC License API + * + * @param language - the language to retrieve the licenses for + * @return a map of licenses with the id and the license for the provided language + */ + public Map retrieveLicenses(String language) { + String ccLicenseUrl = configurationService.getProperty("cc.api.rooturl"); + + String uri = ccLicenseUrl + "/?locale=" + language; + HttpGet httpGet = new HttpGet(uri); + + List licenses; + try (CloseableHttpResponse response = client.execute(httpGet)) { + licenses = retrieveLicenses(response); + } catch (JDOMException | JaxenException | IOException e) { + log.error("Error while retrieving the license details using url: " + uri, e); + licenses = Collections.emptyList(); + } + + Map ccLicenses = new HashMap<>(); + + for (String license : licenses) { + + String licenseUri = ccLicenseUrl + "/license/" + license; + HttpGet licenseHttpGet = new HttpGet(licenseUri); + try (CloseableHttpResponse response = client.execute(licenseHttpGet)) { + CCLicense ccLicense = retrieveLicenseObject(license, response); + ccLicenses.put(ccLicense.getLicenseId(), ccLicense); + } catch (JaxenException | JDOMException | IOException e) { + log.error("Error while retrieving the license details using url: " + licenseUri, e); + } + } + + return ccLicenses; + } + + /** + * Retrieve the list of licenses from the response from the CC License API and remove the licenses configured + * to be excluded + * + * @param response The response from the API + * @return a list of license identifiers for which details need to be retrieved + * @throws IOException + * @throws JaxenException + * @throws JDOMException + */ + private List retrieveLicenses(CloseableHttpResponse response) + throws IOException, JaxenException, JDOMException { + + List domains = new LinkedList<>(); + String[] excludedLicenses = configurationService.getArrayProperty("cc.license.classfilter"); + + + String responseString = EntityUtils.toString(response.getEntity()); + JDOMXPath licenseClassXpath = new JDOMXPath("//licenses/license"); + + + try (StringReader stringReader = new StringReader(responseString)) { + InputSource is = new InputSource(stringReader); + org.jdom.Document classDoc = this.parser.build(is); + + List elements = licenseClassXpath.selectNodes(classDoc); + for (Element element : elements) { + String licenseId = getSingleNodeValue(element, "@id"); + if (StringUtils.isNotBlank(licenseId) && !ArrayUtils.contains(excludedLicenses, licenseId)) { + domains.add(licenseId); + } + } + } + + return domains; + + } + + /** + * Parse the response for a single CC License and return the corresponding CC License Object + * + * @param licenseId the license id of the CC License to retrieve + * @param response for a specific CC License response + * @return the corresponding CC License Object + * @throws IOException + * @throws JaxenException + * @throws JDOMException + */ + private CCLicense retrieveLicenseObject(final String licenseId, CloseableHttpResponse response) + throws IOException, JaxenException, JDOMException { + + String responseString = EntityUtils.toString(response.getEntity()); + + + JDOMXPath licenseClassXpath = new JDOMXPath("//licenseclass"); + JDOMXPath licenseFieldXpath = new JDOMXPath("field"); + + + try (StringReader stringReader = new StringReader(responseString)) { + InputSource is = new InputSource(stringReader); + + org.jdom.Document classDoc = this.parser.build(is); + + Object element = licenseClassXpath.selectSingleNode(classDoc); + String licenseLabel = getSingleNodeValue(element, "label"); + + List ccLicenseFields = new LinkedList<>(); + + List licenseFields = licenseFieldXpath.selectNodes(element); + for (Element licenseField : licenseFields) { + CCLicenseField ccLicenseField = parseLicenseField(licenseField); + ccLicenseFields.add(ccLicenseField); + } + + return new CCLicense(licenseId, licenseLabel, ccLicenseFields); + } + } + + private CCLicenseField parseLicenseField(final Element licenseField) throws JaxenException { + String id = getSingleNodeValue(licenseField, "@id"); + String label = getSingleNodeValue(licenseField, "label"); + String description = getSingleNodeValue(licenseField, "description"); + + JDOMXPath enumXpath = new JDOMXPath("enum"); + List enums = enumXpath.selectNodes(licenseField); + + List ccLicenseFieldEnumList = new LinkedList<>(); + + for (Element enumElement : enums) { + CCLicenseFieldEnum ccLicenseFieldEnum = parseEnum(enumElement); + ccLicenseFieldEnumList.add(ccLicenseFieldEnum); + } + + return new CCLicenseField(id, label, description, ccLicenseFieldEnumList); + + } + + private CCLicenseFieldEnum parseEnum(final Element enumElement) throws JaxenException { + String id = getSingleNodeValue(enumElement, "@id"); + String label = getSingleNodeValue(enumElement, "label"); + String description = getSingleNodeValue(enumElement, "description"); + + return new CCLicenseFieldEnum(id, label, description); + } + + + private String getNodeValue(final Object el) { + if (el instanceof Element) { + return ((Element) el).getValue(); + } else if (el instanceof Attribute) { + return ((Attribute) el).getValue(); + } else if (el instanceof String) { + return (String) el; + } else { + return null; + } + } + + private String getSingleNodeValue(final Object t, String query) throws JaxenException { + JDOMXPath xpath = new JDOMXPath(query); + Object singleNode = xpath.selectSingleNode(t); + + return getNodeValue(singleNode); + } + + /** + * Retrieve the CC License URI based on the provided license id, language and answers to the field questions from + * the CC License API + * + * @param licenseId - the ID of the license + * @param language - the language for which to retrieve the full answerMap + * @param answerMap - the answers to the different field questions + * @return the CC License URI + */ + public String retrieveRightsByQuestion(String licenseId, + String language, + Map answerMap) { + + String ccLicenseUrl = configurationService.getProperty("cc.api.rooturl"); + + + HttpPost httpPost = new HttpPost(ccLicenseUrl + "/license/" + licenseId + "/issue"); + + + String answers = createAnswerString(answerMap); + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + String text = MessageFormat.format(postAnswerFormat, licenseId, language, answers); + builder.addTextBody(postArgument, text); + + HttpEntity multipart = builder.build(); + + httpPost.setEntity(multipart); + + try (CloseableHttpResponse response = client.execute(httpPost)) { + return retrieveLicenseUri(response); + } catch (JDOMException | JaxenException | IOException e) { + log.error("Error while retrieving the license uri for license : " + licenseId + " with answers " + + answerMap.toString(), e); + } + return null; + } + + /** + * Parse the response for the CC License URI request and return the corresponding CC License URI + * + * @param response for a specific CC License URI response + * @return the corresponding CC License URI as a string + * @throws IOException + * @throws JaxenException + * @throws JDOMException + */ + private String retrieveLicenseUri(final CloseableHttpResponse response) + throws IOException, JaxenException, JDOMException { + + String responseString = EntityUtils.toString(response.getEntity()); + JDOMXPath licenseClassXpath = new JDOMXPath("//result/license-uri"); + + + try (StringReader stringReader = new StringReader(responseString)) { + InputSource is = new InputSource(stringReader); + org.jdom.Document classDoc = this.parser.build(is); + + Object node = licenseClassXpath.selectSingleNode(classDoc); + String nodeValue = getNodeValue(node); + + if (StringUtils.isNotBlank(nodeValue)) { + return nodeValue; + } + } + return null; + } + + private String createAnswerString(final Map parameterMap) { + StringBuilder sb = new StringBuilder(); + for (String key : parameterMap.keySet()) { + sb.append("<"); + sb.append(key); + sb.append(">"); + sb.append(parameterMap.get(key)); + sb.append(""); + } + return sb.toString(); + } + + /** + * Retrieve the license RDF document based on the license URI + * + * @param licenseURI - The license URI for which to retrieve the license RDF document + * @return the license RDF document + * @throws IOException + */ + @Override + public Document retrieveLicenseRDFDoc(String licenseURI) throws IOException { + String ccLicenseUrl = configurationService.getProperty("cc.api.rooturl"); + + String issueUrl = ccLicenseUrl + "/details?license-uri=" + licenseURI; + + URL request_url; + try { + request_url = new URL(issueUrl); + } catch (MalformedURLException e) { + return null; + } + URLConnection connection = request_url.openConnection(); + connection.setDoOutput(true); + try { + // parsing document from input stream + InputStream stream = connection.getInputStream(); + Document doc = parser.build(stream); + return doc; + + } catch (Exception e) { + log.error("Error while retrieving the license document for URI: " + licenseURI, e); + } + return null; + } + + /** + * Retrieve the license Name from the license document + * + * @param doc - The license document from which to retrieve the license name + * @return the license name + */ + public String retrieveLicenseName(final Document doc) { + try { + return getSingleNodeValue(doc, "//result/license-name"); + } catch (JaxenException e) { + log.error("Error while retrieving the license name from the license document", e); + } + return null; + } + +} diff --git a/dspace-api/src/main/java/org/dspace/license/CCLicenseField.java b/dspace-api/src/main/java/org/dspace/license/CCLicenseField.java index 6360249f65..8fb6de5478 100644 --- a/dspace-api/src/main/java/org/dspace/license/CCLicenseField.java +++ b/dspace-api/src/main/java/org/dspace/license/CCLicenseField.java @@ -7,8 +7,7 @@ */ package org.dspace.license; -import java.util.HashMap; -import java.util.Map; +import java.util.List; /** * Wrapper class for representation of a license field declaration. @@ -22,7 +21,7 @@ public class CCLicenseField { private String description = ""; private String type = ""; - private HashMap fieldEnum = null; + private List fieldEnum = null; /** * Construct a new LicenseField class. Note that after construction, @@ -31,13 +30,11 @@ public class CCLicenseField { * @param id The unique identifier for this field; this value will be used in constructing the answers XML. * @param label The label to use when generating the user interface. */ - public CCLicenseField(String id, String label) { - super(); - - this.fieldEnum = new HashMap(); - + public CCLicenseField(String id, String label, String description, List fieldEnum) { this.id = id; this.label = label; + this.description = description; + this.fieldEnum = fieldEnum; } /** @@ -90,16 +87,12 @@ public class CCLicenseField { } /** - * @return Returns an instance implementing the Map interface; - * the instance contains a mapping from identifiers to - * labels for the enumeration values. - * @see Map + * Returns the list of enums of this field + * @return the list of enums of this field */ - public Map getEnum() { - return this.fieldEnum; + public List getFieldEnum() { + return fieldEnum; } - - } diff --git a/dspace-api/src/main/java/org/dspace/license/CCLicenseFieldEnum.java b/dspace-api/src/main/java/org/dspace/license/CCLicenseFieldEnum.java new file mode 100644 index 0000000000..628fcb8354 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/license/CCLicenseFieldEnum.java @@ -0,0 +1,82 @@ +/** + * 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.license; + +import org.apache.commons.lang3.StringUtils; + +/** + * Wrapper class for representation of a license field enum declaration. + * A field enum is a single "answer" to the field question + */ +public class CCLicenseFieldEnum { + + private String id = ""; + private String label = ""; + private String description = ""; + + public CCLicenseFieldEnum(String id, String label, String description) { + if (StringUtils.isNotBlank(id)) { + this.id = id; + } + if (StringUtils.isNotBlank(label)) { + this.label = label; + } + if (StringUtils.isNotBlank(description)) { + this.description = description; + } + + } + + /** + * Get the id of this enum + * @return the id of this enum + */ + public String getId() { + return id; + } + + /** + * Set the id of this enum + * @param id + */ + public void setId(final String id) { + this.id = id; + } + + /** + * Get the label of this enum + * @return the label of this enum + */ + public String getLabel() { + return label; + } + + /** + * Set the label of this enum + * @param label + */ + public void setLabel(final String label) { + this.label = label; + } + + /** + * Get the description of this enum + * @return the description of this enum + */ + public String getDescription() { + return description; + } + + /** + * Set the description of this enum + * @param description + */ + public void setDescription(final String description) { + this.description = description; + } +} diff --git a/dspace-api/src/main/java/org/dspace/license/CCLookup.java b/dspace-api/src/main/java/org/dspace/license/CCLookup.java deleted file mode 100644 index c86aa78301..0000000000 --- a/dspace-api/src/main/java/org/dspace/license/CCLookup.java +++ /dev/null @@ -1,435 +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.license; - -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLConnection; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; - -import org.apache.logging.log4j.Logger; -import org.dspace.license.factory.LicenseServiceFactory; -import org.dspace.license.service.CreativeCommonsService; -import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; -import org.jaxen.JaxenException; -import org.jaxen.jdom.JDOMXPath; -import org.jdom.Attribute; -import org.jdom.Document; -import org.jdom.Element; -import org.jdom.JDOMException; -import org.jdom.input.SAXBuilder; - - -/** - * A wrapper around Creative Commons REST web services. - * - * @author Wendy Bossons - */ -public class CCLookup { - - /** - * log4j logger - */ - private static Logger log = org.apache.logging.log4j.LogManager.getLogger(CCLookup.class); - - private String cc_root; - private String jurisdiction; - private List lcFilter = new ArrayList(); - - private Document license_doc = null; - private String rdfString = null; - private String errorMessage = null; - private boolean success = false; - - private SAXBuilder parser = new SAXBuilder(); - private List licenses = new ArrayList(); - private List licenseFields = new ArrayList(); - - protected CreativeCommonsService creativeCommonsService = LicenseServiceFactory.getInstance() - .getCreativeCommonsService(); - - /** - * Constructs a new instance with the default web services root. - */ - public CCLookup() { - super(); - - ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); - - cc_root = configurationService.getProperty("cc.api.rooturl"); - - String jurisProp = configurationService.getProperty("cc.license.jurisdiction"); - jurisdiction = (jurisProp != null) ? jurisProp : ""; - - String[] filters = configurationService.getArrayProperty("cc.license.classfilter"); - if (filters != null) { - for (String name : filters) { - lcFilter.add(name.trim()); - } - } - } - - /** - * Returns the id for a particular CCLicense label. Returns an - * empty string if no match is found. - * - * @param class_label The CCLicense label to find. - * @return Returns a String containing the License class ID if the label - * is found; if not found, returns an empty string. - * @see CCLicense - */ - public String getLicenseId(String class_label) { - for (int i = 0; i < this.licenses.size(); i++) { - if (((CCLicense) this.licenses.get(i)).getLicenseName().equals(class_label)) { - return ((CCLicense) this.licenses.get(i)).getLicenseId(); - } - } - - return ""; - } - - /** - * Queries the web service for the available licenses. - * - * @param language The language to request labels and description strings in. - * @return Returns a Map of CCLicense objects. - * @see Map - * @see CCLicense - */ - public Collection getLicenses(String language) { - - // create XPath expressions - try { - JDOMXPath xp_Licenses = new JDOMXPath("//licenses/license"); - JDOMXPath xp_LicenseID = new JDOMXPath("@id"); - URL classUrl = new URL(this.cc_root + "/?locale=" + language); - Document classDoc = this.parser.build(classUrl); - // extract the identifiers and labels using XPath - List results = xp_Licenses.selectNodes(classDoc); - // populate licenses container - this.licenses.clear(); - for (int i = 0; i < results.size(); i++) { - Element license = results.get(i); - // add if not filtered - String liD = ((Attribute) xp_LicenseID.selectSingleNode(license)).getValue(); - if (!lcFilter.contains(liD)) { - this.licenses.add(new CCLicense(liD, license.getText(), i)); - } - } - } catch (JaxenException jaxen_e) { - return null; - } catch (JDOMException jdom_e) { - return null; - } catch (IOException io_e) { - return null; - } catch (Exception e) { - // do nothing... but we should - return null; - } - - return licenses; - } - - - /** - * Queries the web service for a set of licenseFields for a particular license class. - * - * @param license A String specifying the CCLicense identifier to - * retrieve fields for. - * @param language the locale string - * @return A Collection of LicenseField objects. - * @see CCLicense - */ - public Collection getLicenseFields(String license, String language) { - - JDOMXPath xp_LicenseField; - JDOMXPath xp_LicenseID; - JDOMXPath xp_FieldType; - JDOMXPath xp_Description; - JDOMXPath xp_Label; - JDOMXPath xp_Enum; - - Document fieldDoc; - - URL classUrl; - List results = null; - List enumOptions = null; - - // create XPath expressions - try { - xp_LicenseField = new JDOMXPath("//field"); - xp_LicenseID = new JDOMXPath("@id"); - xp_Description = new JDOMXPath("description"); - xp_Label = new JDOMXPath("label"); - xp_FieldType = new JDOMXPath("type"); - xp_Enum = new JDOMXPath("enum"); - - } catch (JaxenException e) { - return null; - } - - // retrieve and parse the license class document - try { - classUrl = new URL(this.cc_root + "/license/" + license + "?locale=" + language); - } catch (Exception err) { - // do nothing... but we should - return null; - } - - // parse the licenses document - try { - fieldDoc = this.parser.build(classUrl); - } catch (JDOMException e) { - return null; - } catch (IOException e) { - return null; - } - - // reset the field definition container - this.licenseFields.clear(); - - // extract the identifiers and labels using XPath - try { - results = xp_LicenseField.selectNodes(fieldDoc); - } catch (JaxenException e) { - return null; - } - - for (int i = 0; i < results.size(); i++) { - Element field = (Element) results.get(i); - - try { - // create the field object - CCLicenseField cclicensefield = new CCLicenseField( - ((Attribute) xp_LicenseID.selectSingleNode(field)).getValue(), - ((Element) xp_Label.selectSingleNode(field)).getText()); - - // extract additional properties - cclicensefield.setDescription(((Element) xp_Description.selectSingleNode(field)).getText()); - cclicensefield.setType(((Element) xp_FieldType.selectSingleNode(field)).getText()); - - enumOptions = xp_Enum.selectNodes(field); - - for (int j = 0; j < enumOptions.size(); j++) { - String id = ((Attribute) xp_LicenseID.selectSingleNode(enumOptions.get(j))).getValue(); - String label = ((Element) xp_Label.selectSingleNode(enumOptions.get(j))).getText(); - - cclicensefield.getEnum().put(id, label); - - } // for each enum option - - this.licenseFields.add(cclicensefield); - } catch (JaxenException e) { - return null; - } - } - - return licenseFields; - } // licenseFields - - /** - * Passes a set of "answers" to the web service and retrieves a license. - * - * @param licenseId The identifier of the license class being requested. - * @param answers A Map containing the answers to the license fields; - * each key is the identifier of a LicenseField, with the value - * containing the user-supplied answer. - * @param lang The language to request localized elements in. - * @throws IOException if IO error - * @see CCLicense - * @see Map - */ - public void issue(String licenseId, Map answers, String lang) - throws IOException { - - // Determine the issue URL - String issueUrl = this.cc_root + "/license/" + licenseId + "/issue"; - // Assemble the "answers" document - String answer_doc = "\n" + lang + "\n" + "\n"; - Iterator keys = answers.keySet().iterator(); - - try { - String current = (String) keys.next(); - - while (true) { - answer_doc += "<" + current + ">" + (String) answers.get(current) + "\n"; - current = (String) keys.next(); - } - - - } catch (NoSuchElementException e) { - // exception indicates we've iterated through the - // entire collection; just swallow and continue - } - // answer_doc += "\n"; FAILS with jurisdiction argument - answer_doc += "\n\n"; - String post_data; - - try { - post_data = URLEncoder.encode("answers", "UTF-8") + "=" + URLEncoder.encode(answer_doc, "UTF-8"); - } catch (UnsupportedEncodingException e) { - return; - } - - URL post_url; - try { - post_url = new URL(issueUrl); - } catch (MalformedURLException e) { - return; - } - URLConnection connection = post_url.openConnection(); - // this will not be needed after I'm done TODO: remove - connection.setDoOutput(true); - OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream()); - writer.write(post_data); - writer.flush(); - // end TODO - try { - // parsing document from input stream - java.io.InputStream stream = connection.getInputStream(); - this.license_doc = this.parser.build(stream); - } catch (JDOMException jde) { - log.warn(jde.getMessage()); - } catch (Exception e) { - log.warn(e.getCause()); - } - return; - } // issue - - /** - * Passes a set of "answers" to the web service and retrieves a license. - * - * @param licenseURI The uri of the license. - * - * Note: does not support localization in 1.5 -- not yet - * @throws IOException if IO error - * @see CCLicense - * @see Map - */ - public void issue(String licenseURI) - throws IOException { - - // Determine the issue URL - // Example: http://api.creativecommons.org/rest/1.5/details? - // license-uri=http://creativecommons.org/licenses/by-nc-sa/3.0/ - String issueUrl = cc_root + "/details?license-uri=" + licenseURI; - - URL request_url; - try { - request_url = new URL(issueUrl); - } catch (MalformedURLException e) { - return; - } - URLConnection connection = request_url.openConnection(); - // this will not be needed after I'm done TODO: remove - connection.setDoOutput(true); - try { - // parsing document from input stream - java.io.InputStream stream = connection.getInputStream(); - license_doc = this.parser.build(stream); - } catch (JDOMException jde) { - log.warn(jde.getMessage()); - } catch (Exception e) { - log.warn(e.getCause()); - } - return; - } // issue - - /** - * Retrieves the URI for the license issued. - * - * @return A String containing the URI for the license issued. - */ - public String getLicenseUrl() { - String text = null; - try { - JDOMXPath xp_LicenseName = new JDOMXPath("//result/license-uri"); - text = ((Element) xp_LicenseName.selectSingleNode(this.license_doc)).getText(); - } catch (Exception e) { - log.warn(e.getMessage()); - setSuccess(false); - text = "An error occurred getting the license - uri."; - } finally { - return text; - } - } // getLicenseUrl - - /** - * Retrieves the human readable name for the license issued. - * - * @return A String containing the license name. - */ - public String getLicenseName() { - String text = null; - try { - JDOMXPath xp_LicenseName = new JDOMXPath("//result/license-name"); - text = ((Element) xp_LicenseName.selectSingleNode(this.license_doc)).getText(); - } catch (Exception e) { - log.warn(e.getMessage()); - setSuccess(false); - text = "An error occurred on the license name."; - } finally { - return text; - } - } // getLicenseName - - - public org.jdom.Document getLicenseDocument() { - return this.license_doc; - } - - public String getRdf() - throws IOException { - String result = ""; - try { - result = creativeCommonsService.fetchLicenseRDF(license_doc); - } catch (Exception e) { - log.warn("An error occurred getting the rdf . . ." + e.getMessage()); - setSuccess(false); - } - return result; - } - - public boolean isSuccess() { - setSuccess(false); - JDOMXPath xp_Success; - String text = null; - try { - xp_Success = new JDOMXPath("//message"); - text = ((Element) xp_Success.selectSingleNode(this.license_doc)).getText(); - setErrorMessage(text); - } catch (Exception e) { - log.warn("There was an issue . . . " + text); - setSuccess(true); - } - return this.success; - } - - private void setSuccess(boolean success) { - this.success = success; - } - - public String getErrorMessage() { - return this.errorMessage; - } - - private void setErrorMessage(String errorMessage) { - this.errorMessage = errorMessage; - } - -} diff --git a/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java b/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java index 384b82ddc3..40e727d9df 100644 --- a/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/license/CreativeCommonsServiceImpl.java @@ -13,7 +13,10 @@ import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; import java.sql.SQLException; +import java.util.HashMap; +import java.util.LinkedList; import java.util.List; +import java.util.Map; import javax.xml.transform.Templates; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; @@ -82,9 +85,18 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi protected BundleService bundleService; @Autowired(required = true) protected ItemService itemService; + @Autowired + protected CCLicenseConnectorService ccLicenseConnectorService; protected ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + private String defaultLanguage; + private String jurisdiction; + private static final String JURISDICTION_KEY = "jurisdiction"; + + + private Map> ccLicenses; + protected CreativeCommonsServiceImpl() { } @@ -101,10 +113,14 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi System.setProperty("http.proxyPort", proxyPort); } + ccLicenses = new HashMap<>(); + defaultLanguage = configurationService.getProperty("cc.license.locale", "en"); + jurisdiction = configurationService.getProperty("cc.license.jurisdiction", ""); + try { templates = TransformerFactory.newInstance().newTemplates( - new StreamSource(CreativeCommonsServiceImpl.class - .getResourceAsStream("CreativeCommons.xsl"))); + new StreamSource(CreativeCommonsServiceImpl.class + .getResourceAsStream("CreativeCommons.xsl"))); } catch (TransformerConfigurationException e) { throw new RuntimeException(e.getMessage(), e); } @@ -112,15 +128,10 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi } - @Override - public boolean isEnabled() { - return true; - } - // create the CC bundle if it doesn't exist // If it does, remove it and create a new one. protected Bundle getCcBundle(Context context, Item item) - throws SQLException, AuthorizeException, IOException { + throws SQLException, AuthorizeException, IOException { List bundles = itemService.getBundles(item, CC_BUNDLE_NAME); if ((bundles.size() > 0) && (bundles.get(0) != null)) { @@ -131,8 +142,8 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi @Override public void setLicenseRDF(Context context, Item item, String licenseRdf) - throws SQLException, IOException, - AuthorizeException { + throws SQLException, IOException, + AuthorizeException { Bundle bundle = getCcBundle(context, item); // set the format BitstreamFormat bs_rdf_format = bitstreamFormatService.findByShortDescription(context, "RDF XML"); @@ -144,7 +155,7 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi @Override public void setLicense(Context context, Item item, InputStream licenseStm, String mimeType) - throws SQLException, IOException, AuthorizeException { + throws SQLException, IOException, AuthorizeException { Bundle bundle = getCcBundle(context, item); // set the format @@ -160,17 +171,26 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi Bitstream bs = bitstreamService.create(context, bundle, licenseStm); bs.setSource(context, CC_BS_SOURCE); bs.setName(context, (mimeType != null && - (mimeType.equalsIgnoreCase("text/xml") || - mimeType.equalsIgnoreCase("text/rdf"))) ? - BSN_LICENSE_RDF : BSN_LICENSE_TEXT); + (mimeType.equalsIgnoreCase("text/xml") || + mimeType.equalsIgnoreCase("text/rdf"))) ? + BSN_LICENSE_RDF : BSN_LICENSE_TEXT); bs.setFormat(context, bs_format); bitstreamService.update(context, bs); } + /** + * Removes the license file from the item + * + * @param context - The relevant DSpace Context + * @param item - The item from which the license file needs to be removed + * @throws SQLException + * @throws IOException + * @throws AuthorizeException + */ @Override - public void removeLicense(Context context, Item item) - throws SQLException, IOException, AuthorizeException { + public void removeLicenseFile(Context context, Item item) + throws SQLException, IOException, AuthorizeException { // remove CC license bundle if one exists List bundles = itemService.getBundles(item, CC_BUNDLE_NAME); @@ -179,66 +199,74 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi } } - @Override - public boolean hasLicense(Context context, Item item) - throws SQLException, IOException { - // try to find CC license bundle - List bundles = itemService.getBundles(item, CC_BUNDLE_NAME); - - if (bundles.size() == 0) { - return false; - } - - // verify it has correct contents - try { - if ((getLicenseURL(context, item) == null)) { - return false; - } - } catch (AuthorizeException ae) { - return false; - } - - return true; - } - - @Override - public String getLicenseRDF(Context context, Item item) throws SQLException, - IOException, AuthorizeException { - return getStringFromBitstream(context, item, BSN_LICENSE_RDF); - } - @Override public Bitstream getLicenseRdfBitstream(Item item) throws SQLException, - IOException, AuthorizeException { + IOException, AuthorizeException { return getBitstream(item, BSN_LICENSE_RDF); } @Deprecated @Override public Bitstream getLicenseTextBitstream(Item item) throws SQLException, - IOException, AuthorizeException { + IOException, AuthorizeException { return getBitstream(item, BSN_LICENSE_TEXT); } @Override public String getLicenseURL(Context context, Item item) throws SQLException, IOException, AuthorizeException { - String licenseUri = getCCField("uri").ccItemValue(item); + String licenseUri = getCCField("uri"); if (StringUtils.isNotBlank(licenseUri)) { - return licenseUri; + return getLicenseURI(item); } // JSPUI backward compatibility see https://jira.duraspace.org/browse/DS-2604 return getStringFromBitstream(context, item, BSN_LICENSE_URL); } + /** + * Returns the stored license uri of the item + * + * @param item - The item for which to retrieve the stored license uri + * @return the stored license uri of the item + */ + @Override + public String getLicenseURI(Item item) { + String licenseUriField = getCCField("uri"); + if (StringUtils.isNotBlank(licenseUriField)) { + String metadata = itemService.getMetadata(item, licenseUriField); + if (StringUtils.isNotBlank(metadata)) { + return metadata; + } + } + return null; + } + + /** + * Returns the stored license name of the item + * + * @param item - The item for which to retrieve the stored license name + * @return the stored license name of the item + */ + @Override + public String getLicenseName( Item item) { + String licenseNameField = getCCField("name"); + if (StringUtils.isNotBlank(licenseNameField)) { + String metadata = itemService.getMetadata(item, licenseNameField); + if (StringUtils.isNotBlank(metadata)) { + return metadata; + } + } + return null; + } + @Override public String fetchLicenseRDF(Document license) { StringWriter result = new StringWriter(); try { templates.newTransformer().transform( - new JDOMSource(license), - new StreamResult(result) + new JDOMSource(license), + new StreamResult(result) ); } catch (TransformerException e) { throw new IllegalStateException(e.getMessage(), e); @@ -267,7 +295,7 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi */ protected void setBitstreamFromBytes(Context context, Item item, Bundle bundle, String bitstream_name, BitstreamFormat format, byte[] bytes) - throws SQLException, IOException, AuthorizeException { + throws SQLException, IOException, AuthorizeException { ByteArrayInputStream bais = new ByteArrayInputStream(bytes); Bitstream bs = bitstreamService.create(context, bundle, bais); @@ -297,7 +325,7 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi */ protected String getStringFromBitstream(Context context, Item item, String bitstream_name) throws SQLException, IOException, - AuthorizeException { + AuthorizeException { byte[] bytes = getBytesFromBitstream(context, item, bitstream_name); if (bytes == null) { @@ -320,7 +348,7 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi * to perform a particular action. */ protected Bitstream getBitstream(Item item, String bitstream_name) - throws SQLException, IOException, AuthorizeException { + throws SQLException, IOException, AuthorizeException { Bundle cc_bundle = null; // look for the CC bundle @@ -342,7 +370,7 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi } protected byte[] getBytesFromBitstream(Context context, Item item, String bitstream_name) - throws SQLException, IOException, AuthorizeException { + throws SQLException, IOException, AuthorizeException { Bitstream bs = getBitstream(item, bitstream_name); // no such bitstream @@ -361,26 +389,322 @@ public class CreativeCommonsServiceImpl implements CreativeCommonsService, Initi * Returns a metadata field handle for given field Id */ @Override - public LicenseMetadataValue getCCField(String fieldId) { - return new LicenseMetadataValue(configurationService.getProperty("cc.license." + fieldId)); + public String getCCField(String fieldId) { + return configurationService.getProperty("cc.license." + fieldId); } + /** + * Remove license information, delete also the bitstream + * + * @param context - DSpace Context + * @param item - the item + * @throws AuthorizeException Exception indicating the current user of the context does not have permission + * to perform a particular action. + * @throws IOException A general class of exceptions produced by failed or interrupted I/O operations. + * @throws SQLException An exception that provides information on a database access error or other errors. + */ @Override - public void removeLicense(Context context, LicenseMetadataValue uriField, - LicenseMetadataValue nameField, Item item) - throws AuthorizeException, IOException, SQLException { + public void removeLicense(Context context, Item item) + throws AuthorizeException, IOException, SQLException { + + String uriField = getCCField("uri"); + String nameField = getCCField("name"); + + String licenseUri = itemService.getMetadata(item, uriField); + // only remove any previous licenses - String licenseUri = uriField.ccItemValue(item); if (licenseUri != null) { - uriField.removeItemValue(context, item, licenseUri); + removeLicenseField(context, item, uriField); if (configurationService.getBooleanProperty("cc.submit.setname")) { - String licenseName = nameField.keyedItemValue(item, licenseUri); - nameField.removeItemValue(context, item, licenseName); + removeLicenseField(context, item, nameField); } if (configurationService.getBooleanProperty("cc.submit.addbitstream")) { - removeLicense(context, item); + removeLicenseFile(context, item); } } } + private void removeLicenseField(Context context, Item item, String field) throws SQLException { + String[] params = splitField(field); + itemService.clearMetadata(context, item, params[0], params[1], params[2], params[3]); + + } + + private void addLicenseField(Context context, Item item, String field, String value) throws SQLException { + String[] params = splitField(field); + itemService.addMetadata(context, item, params[0], params[1], params[2], params[3], value); + + } + + /** + * Find all CC Licenses using the default language found in the configuration + * + * @return A list of available CC Licenses + */ + @Override + public List findAllCCLicenses() { + return findAllCCLicenses(defaultLanguage); + } + + /** + * Find all CC Licenses for the provided language + * + * @param language - the language for which to find the CC Licenses + * @return A list of available CC Licenses for the provided language + */ + @Override + public List findAllCCLicenses(String language) { + + if (!ccLicenses.containsKey(language)) { + initLicenses(language); + } + return new LinkedList<>(ccLicenses.get(language).values()); + } + + /** + * Find the CC License corresponding to the provided ID using the default language found in the configuration + * + * @param id - the ID of the license to be found + * @return the corresponding license if found or null when not found + */ + @Override + public CCLicense findOne(String id) { + return findOne(id, defaultLanguage); + } + + /** + * Find the CC License corresponding to the provided ID and provided language + * + * @param id - the ID of the license to be found + * @param language - the language for which to find the CC License + * @return the corresponding license if found or null when not found + */ + @Override + public CCLicense findOne(String id, String language) { + if (!ccLicenses.containsKey(language)) { + initLicenses(language); + } + Map licenseMap = ccLicenses.get(language); + if (licenseMap.containsKey(id)) { + return licenseMap.get(id); + } + return null; + } + + /** + * Retrieves the licenses for a specific language and cache them in this service + * + * @param language - the language for which to find the CC Licenses + */ + private void initLicenses(final String language) { + Map licenseMap = ccLicenseConnectorService.retrieveLicenses(language); + ccLicenses.put(language, licenseMap); + } + + /** + * Retrieve the CC License URI for the provided license ID, based on the provided answers, using the default + * language found in the configuration + * + * @param licenseId - the ID of the license + * @param answerMap - the answers to the different field questions + * @return the corresponding license URI + */ + @Override + public String retrieveLicenseUri(String licenseId, Map answerMap) { + return retrieveLicenseUri(licenseId, defaultLanguage, answerMap); + + } + + /** + * Retrieve the CC License URI for the provided license ID and language based on the provided answers + * + * @param licenseId - the ID of the license + * @param language - the language for which to find the CC License URI + * @param answerMap - the answers to the different field questions + * @return the corresponding license URI + */ + @Override + public String retrieveLicenseUri(String licenseId, String language, Map answerMap) { + return ccLicenseConnectorService.retrieveRightsByQuestion(licenseId, language, answerMap); + + } + + /** + * Verify whether the answer map contains a valid response to all field questions and no answers that don't have a + * corresponding question in the license, using the default language found in the config to check the license + * + * @param licenseId - the ID of the license + * @param fullAnswerMap - the answers to the different field questions + * @return whether the information is valid + */ + @Override + public boolean verifyLicenseInformation(String licenseId, Map fullAnswerMap) { + return verifyLicenseInformation(licenseId, defaultLanguage, fullAnswerMap); + } + + /** + * Verify whether the answer map contains a valid response to all field questions and no answers that don't have a + * corresponding question in the license, using the provided language to check the license + * + * @param licenseId - the ID of the license + * @param language - the language for which to retrieve the full answerMap + * @param fullAnswerMap - the answers to the different field questions + * @return whether the information is valid + */ + @Override + public boolean verifyLicenseInformation(String licenseId, String language, Map fullAnswerMap) { + CCLicense ccLicense = findOne(licenseId, language); + + List ccLicenseFieldList = ccLicense.getCcLicenseFieldList(); + + for (String field : fullAnswerMap.keySet()) { + CCLicenseField ccLicenseField = findCCLicenseField(field, ccLicenseFieldList); + if (ccLicenseField == null) { + return false; + } + if (!containsAnswerEnum(fullAnswerMap.get(field), ccLicenseField)) { + return false; + } + } + return true; + } + + /** + * Retrieve the full answer map containing empty values when an answer for a field was not provided in the + * answerMap, using the default language found in the configuration + * + * @param licenseId - the ID of the license + * @param answerMap - the answers to the different field questions + * @return the answerMap supplemented with all other license fields with a blank answer + */ + @Override + public Map retrieveFullAnswerMap(String licenseId, Map answerMap) { + return retrieveFullAnswerMap(licenseId, defaultLanguage, answerMap); + } + + /** + * Retrieve the full answer map for a provided language, containing empty values when an answer for a field was not + * provided in the answerMap. + * + * @param licenseId - the ID of the license + * @param language - the language for which to retrieve the full answerMap + * @param answerMap - the answers to the different field questions + * @return the answerMap supplemented with all other license fields with a blank answer for the provided language + */ + @Override + public Map retrieveFullAnswerMap(String licenseId, String language, Map answerMap) { + CCLicense ccLicense = findOne(licenseId, language); + if (ccLicense == null) { + return null; + } + Map fullParamMap = new HashMap<>(answerMap); + List ccLicenseFieldList = ccLicense.getCcLicenseFieldList(); + for (CCLicenseField ccLicenseField : ccLicenseFieldList) { + if (!fullParamMap.containsKey(ccLicenseField.getId())) { + fullParamMap.put(ccLicenseField.getId(), ""); + } + } + + updateJurisdiction(fullParamMap); + + return fullParamMap; + } + + private void updateJurisdiction(final Map fullParamMap) { + if (fullParamMap.containsKey(JURISDICTION_KEY)) { + fullParamMap.put(JURISDICTION_KEY, jurisdiction); + } + } + + private boolean containsAnswerEnum(final String enumAnswer, final CCLicenseField ccLicenseField) { + List fieldEnums = ccLicenseField.getFieldEnum(); + for (CCLicenseFieldEnum fieldEnum : fieldEnums) { + if (StringUtils.equals(fieldEnum.getId(), enumAnswer)) { + return true; + } + } + return false; + } + + private CCLicenseField findCCLicenseField(final String field, final List ccLicenseFieldList) { + for (CCLicenseField ccLicenseField : ccLicenseFieldList) { + if (StringUtils.equals(ccLicenseField.getId(), field)) { + return ccLicenseField; + } + } + + return null; + } + + /** + * Update the license of the item with a new one based on the provided license URI + * + * @param context - The relevant DSpace context + * @param licenseUri - The license URI to be used in the update + * @param item - The item for which to update the license + * @return true when the update was successful, false when not + * @throws AuthorizeException + * @throws SQLException + */ + @Override + public boolean updateLicense(final Context context, final String licenseUri, final Item item) + throws AuthorizeException, SQLException { + try { + Document doc = ccLicenseConnectorService.retrieveLicenseRDFDoc(licenseUri); + if (doc == null) { + return false; + } + String licenseName = ccLicenseConnectorService.retrieveLicenseName(doc); + if (StringUtils.isBlank(licenseName)) { + return false; + } + + removeLicense(context, item); + addLicense(context, item, licenseUri, licenseName, doc); + + return true; + + } catch (IOException e) { + log.error("Error while updating the license of item: " + item.getID(), e); + } + return false; + } + + /** + * Add a new license to the item + * + * @param context - The relevant Dspace context + * @param item - The item to which the license will be added + * @param licenseUri - The license URI to add + * @param licenseName - The license name to add + * @param doc - The license to document to add + * @throws SQLException + * @throws IOException + * @throws AuthorizeException + */ + @Override + public void addLicense(Context context, Item item, String licenseUri, String licenseName, Document doc) + throws SQLException, IOException, AuthorizeException { + String uriField = getCCField("uri"); + String nameField = getCCField("name"); + + addLicenseField(context, item, uriField, licenseUri); + if (configurationService.getBooleanProperty("cc.submit.addbitstream")) { + setLicenseRDF(context, item, fetchLicenseRDF(doc)); + } + if (configurationService.getBooleanProperty("cc.submit.setname")) { + addLicenseField(context, item, nameField, licenseName); + } + } + + private String[] splitField(String fieldName) { + String[] params = new String[4]; + String[] fParams = fieldName.split("\\."); + for (int i = 0; i < fParams.length; i++) { + params[i] = fParams[i]; + } + params[3] = Item.ANY; + return params; + } + } diff --git a/dspace-api/src/main/java/org/dspace/license/LicenseMetadataValue.java b/dspace-api/src/main/java/org/dspace/license/LicenseMetadataValue.java deleted file mode 100644 index ec5c9e447b..0000000000 --- a/dspace-api/src/main/java/org/dspace/license/LicenseMetadataValue.java +++ /dev/null @@ -1,129 +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.license; - -import java.io.IOException; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; - -import org.dspace.authorize.AuthorizeException; -import org.dspace.content.Item; -import org.dspace.content.MetadataValue; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.ItemService; -import org.dspace.core.Context; - -/** - * Helper class for using CC-related Metadata fields - * - * @author kevinvandevelde at atmire.com - */ -public class LicenseMetadataValue { - - protected final ItemService itemService; - // Shibboleth for Creative Commons license data - i.e. characters that reliably indicate CC in a URI - protected static final String ccShib = "creativecommons"; - - private String[] params = new String[4]; - - public LicenseMetadataValue(String fieldName) { - if (fieldName != null && fieldName.length() > 0) { - String[] fParams = fieldName.split("\\."); - for (int i = 0; i < fParams.length; i++) { - params[i] = fParams[i]; - } - params[3] = Item.ANY; - } - itemService = ContentServiceFactory.getInstance().getItemService(); - } - - /** - * Returns first value that matches Creative Commons 'shibboleth', - * or null if no matching values. - * NB: this method will succeed only for metadata fields holding CC URIs - * - * @param item - the item to read - * @return value - the first CC-matched value, or null if no such value - */ - public String ccItemValue(Item item) { - List dcvalues = itemService.getMetadata(item, params[0], params[1], params[2], params[3]); - for (MetadataValue dcvalue : dcvalues) { - if ((dcvalue.getValue()).indexOf(ccShib) != -1) { - // return first value that matches the shib - return dcvalue.getValue(); - } - } - return null; - } - - /** - * Returns the value that matches the value mapped to the passed key if any. - * NB: this only delivers a license name (if present in field) given a license URI - * - * @param item - the item to read - * @param key - the key for desired value - * @return value - the value associated with key or null if no such value - * @throws IOException A general class of exceptions produced by failed or interrupted I/O operations. - * @throws SQLException An exception that provides information on a database access error or other errors. - * @throws AuthorizeException Exception indicating the current user of the context does not have permission - * to perform a particular action. - */ - public String keyedItemValue(Item item, String key) - throws AuthorizeException, IOException, SQLException { - CCLookup ccLookup = new CCLookup(); - ccLookup.issue(key); - String matchValue = ccLookup.getLicenseName(); - List dcvalues = itemService.getMetadata(item, params[0], params[1], params[2], params[3]); - for (MetadataValue dcvalue : dcvalues) { - if (dcvalue.getValue().equals(matchValue)) { - return dcvalue.getValue(); - } - } - return null; - } - - /** - * Removes the passed value from the set of values for the field in passed item. - * - * @param context The relevant DSpace Context. - * @param item - the item to update - * @param value - the value to remove - * @throws IOException A general class of exceptions produced by failed or interrupted I/O operations. - * @throws SQLException An exception that provides information on a database access error or other errors. - * @throws AuthorizeException Exception indicating the current user of the context does not have permission - * to perform a particular action. - */ - public void removeItemValue(Context context, Item item, String value) - throws AuthorizeException, IOException, SQLException { - if (value != null) { - List dcvalues = itemService.getMetadata(item, params[0], params[1], params[2], params[3]); - ArrayList arrayList = new ArrayList(); - for (MetadataValue dcvalue : dcvalues) { - if (!dcvalue.getValue().equals(value)) { - arrayList.add(dcvalue.getValue()); - } - } - itemService.clearMetadata(context, item, params[0], params[1], params[2], params[3]); - itemService.addMetadata(context, item, params[0], params[1], params[2], params[3], arrayList); - } - } - - /** - * Adds passed value to the set of values for the field in passed item. - * - * @param context The relevant DSpace Context. - * @param item - the item to update - * @param value - the value to add in this field - * @throws SQLException An exception that provides information on a database access error or other errors. - */ - public void addItemValue(Context context, Item item, String value) throws SQLException { - itemService.addMetadata(context, item, params[0], params[1], params[2], params[3], value); - } - -} diff --git a/dspace-api/src/main/java/org/dspace/license/service/CreativeCommonsService.java b/dspace-api/src/main/java/org/dspace/license/service/CreativeCommonsService.java index c99c38a127..fa32cb75ca 100644 --- a/dspace-api/src/main/java/org/dspace/license/service/CreativeCommonsService.java +++ b/dspace-api/src/main/java/org/dspace/license/service/CreativeCommonsService.java @@ -10,12 +10,14 @@ package org.dspace.license.service; import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; +import java.util.List; +import java.util.Map; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; import org.dspace.content.Item; import org.dspace.core.Context; -import org.dspace.license.LicenseMetadataValue; +import org.dspace.license.CCLicense; import org.jdom.Document; /** @@ -29,13 +31,6 @@ public interface CreativeCommonsService { public static final String CC_BUNDLE_NAME = "CC-LICENSE"; - /** - * Simple accessor for enabling of CC - * - * @return is CC enabled? - */ - public boolean isEnabled(); - /** * setLicenseRDF * @@ -50,7 +45,7 @@ public interface CreativeCommonsService { * to perform a particular action. */ public void setLicenseRDF(Context context, Item item, String licenseRdf) - throws SQLException, IOException, AuthorizeException; + throws SQLException, IOException, AuthorizeException; /** @@ -72,19 +67,40 @@ public interface CreativeCommonsService { */ public void setLicense(Context context, Item item, InputStream licenseStm, String mimeType) - throws SQLException, IOException, AuthorizeException; + throws SQLException, IOException, AuthorizeException; - public void removeLicense(Context context, Item item) - throws SQLException, IOException, AuthorizeException; + /** + * Removes the license file from the item + * + * @param context - The relevant DSpace Context + * @param item - The item from which the license file needs to be removed + * @throws SQLException + * @throws IOException + * @throws AuthorizeException + */ + public void removeLicenseFile(Context context, Item item) + throws SQLException, IOException, AuthorizeException; - public boolean hasLicense(Context context, Item item) - throws SQLException, IOException; public String getLicenseURL(Context context, Item item) - throws SQLException, IOException, AuthorizeException; + throws SQLException, IOException, AuthorizeException; - public String getLicenseRDF(Context context, Item item) - throws SQLException, IOException, AuthorizeException; + + /** + * Returns the stored license uri of the item + * + * @param item - The item for which to retrieve the stored license uri + * @return the stored license uri of the item + */ + public String getLicenseURI(Item item); + + /** + * Returns the stored license name of the item + * + * @param item - The item for which to retrieve the stored license name + * @return the stored license name of the item + */ + public String getLicenseName(Item item); /** * Get Creative Commons license RDF, returning Bitstream object. @@ -97,7 +113,7 @@ public interface CreativeCommonsService { * to perform a particular action. */ public Bitstream getLicenseRdfBitstream(Item item) - throws SQLException, IOException, AuthorizeException; + throws SQLException, IOException, AuthorizeException; /** * Get Creative Commons license Text, returning Bitstream object. @@ -112,7 +128,7 @@ public interface CreativeCommonsService { * is no longer stored (see https://jira.duraspace.org/browse/DS-2604) */ public Bitstream getLicenseTextBitstream(Item item) - throws SQLException, IOException, AuthorizeException; + throws SQLException, IOException, AuthorizeException; /** * Get a few license-specific properties. We expect these to be cached at @@ -121,7 +137,7 @@ public interface CreativeCommonsService { * @param fieldId name of the property. * @return its value. */ - public LicenseMetadataValue getCCField(String fieldId); + public String getCCField(String fieldId); /** * Apply same transformation on the document to retrieve only the most @@ -138,15 +154,134 @@ public interface CreativeCommonsService { * Remove license information, delete also the bitstream * * @param context - DSpace Context - * @param uriField - the metadata field for license uri - * @param nameField - the metadata field for license name * @param item - the item * @throws AuthorizeException Exception indicating the current user of the context does not have permission * to perform a particular action. * @throws IOException A general class of exceptions produced by failed or interrupted I/O operations. * @throws SQLException An exception that provides information on a database access error or other errors. */ - public void removeLicense(Context context, LicenseMetadataValue uriField, - LicenseMetadataValue nameField, Item item) - throws AuthorizeException, IOException, SQLException; + public void removeLicense(Context context, Item item) + throws AuthorizeException, IOException, SQLException; + + /** + * Find all CC Licenses using the default language found in the configuration + * + * @return A list of available CC Licenses + */ + public List findAllCCLicenses(); + + /** + * Find all CC Licenses for the provided language + * + * @param language - the language for which to find the CC Licenses + * @return A list of available CC Licenses for the provided language + */ + public List findAllCCLicenses(String language); + + /** + * Find the CC License corresponding to the provided ID using the default language found in the configuration + * + * @param id - the ID of the license to be found + * @return the corresponding license if found or null when not found + */ + public CCLicense findOne(String id); + + /** + * Find the CC License corresponding to the provided ID and provided language + * + * @param id - the ID of the license to be found + * @param language - the language for which to find the CC License + * @return the corresponding license if found or null when not found + */ + public CCLicense findOne(String id, String language); + + /** + * Retrieve the CC License URI for the provided license ID, based on the provided answers, using the default + * language found in the configuration + * + * @param licenseId - the ID of the license + * @param answerMap - the answers to the different field questions + * @return the corresponding license URI + */ + public String retrieveLicenseUri(String licenseId, Map answerMap); + + /** + * Retrieve the CC License URI for the provided license ID and language based on the provided answers + * + * @param licenseId - the ID of the license + * @param language - the language for which to find the CC License URI + * @param answerMap - the answers to the different field questions + * @return the corresponding license URI + */ + public String retrieveLicenseUri(String licenseId, String language, Map answerMap); + + /** + * Retrieve the full answer map containing empty values when an answer for a field was not provided in the + * answerMap, using the default language found in the configuration + * + * @param licenseId - the ID of the license + * @param answerMap - the answers to the different field questions + * @return the answerMap supplemented with all other license fields with a blank answer + */ + public Map retrieveFullAnswerMap(String licenseId, Map answerMap); + + /** + * Retrieve the full answer map for a provided language, containing empty values when an answer for a field was not + * provided in the answerMap. + * + * @param licenseId - the ID of the license + * @param language - the language for which to retrieve the full answerMap + * @param answerMap - the answers to the different field questions + * @return the answerMap supplemented with all other license fields with a blank answer for the provided language + */ + public Map retrieveFullAnswerMap(String licenseId, String language, Map answerMap); + + /** + * Verify whether the answer map contains a valid response to all field questions and no answers that don't have a + * corresponding question in the license, using the default language found in the config to check the license + * + * @param licenseId - the ID of the license + * @param fullAnswerMap - the answers to the different field questions + * @return whether the information is valid + */ + public boolean verifyLicenseInformation(String licenseId, Map fullAnswerMap); + + /** + * Verify whether the answer map contains a valid response to all field questions and no answers that don't have a + * corresponding question in the license, using the provided language to check the license + * + * @param licenseId - the ID of the license + * @param language - the language for which to retrieve the full answerMap + * @param fullAnswerMap - the answers to the different field questions + * @return whether the information is valid + */ + public boolean verifyLicenseInformation(String licenseId, String language, Map fullAnswerMap); + + /** + * Update the license of the item with a new one based on the provided license URI + * + * @param context - The relevant DSpace context + * @param licenseUri - The license URI to be used in the update + * @param item - The item for which to update the license + * @return true when the update was successful, false when not + * @throws AuthorizeException + * @throws SQLException + */ + public boolean updateLicense(final Context context, String licenseUri, final Item item) + throws AuthorizeException, SQLException; + + /** + * Add a new license to the item + * + * @param context - The relevant Dspace context + * @param item - The item to which the license will be added + * @param licenseUri - The license URI to add + * @param licenseName - The license name to add + * @param doc - The license to document to add + * @throws SQLException + * @throws IOException + * @throws AuthorizeException + */ + public void addLicense(Context context, Item item, String licenseUri, String licenseName, Document doc) + throws SQLException, IOException, AuthorizeException; } diff --git a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java index 4ce1c5063a..d0fffdb57d 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java +++ b/dspace-api/src/main/java/org/dspace/scripts/DSpaceRunnable.java @@ -7,70 +7,72 @@ */ package org.dspace.scripts; -import java.sql.SQLException; +import java.io.InputStream; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.Option; 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.apache.commons.lang3.StringUtils; +import org.dspace.eperson.EPerson; +import org.dspace.scripts.configuration.ScriptConfiguration; 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 + * This is the class that should be extended for each Script. This class will contain the logic needed to run and it'll + * fetch the information that it needs from the {@link ScriptConfiguration} provided through the diamond operators. + * This will be the dspaceRunnableClass for the {@link ScriptConfiguration} beans. Specifically created for each + * script + * @param */ -public abstract class DSpaceRunnable implements Runnable { +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 + * This EPerson identifier variable is the uuid of the eperson that's running the script */ - protected Options options; + private UUID epersonIdentifier; + /** * 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; + /** + * This method will return the Configuration that the implementing DSpaceRunnable uses + * @return The {@link ScriptConfiguration} that this implementing DspaceRunnable uses + */ + public abstract T getScriptConfiguration(); - public String getName() { - return name; + + private void setHandler(DSpaceRunnableHandler dSpaceRunnableHandler) { + this.handler = dSpaceRunnableHandler; } - @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 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 + * @param currentUser + * @throws ParseException If something goes wrong + */ + public void initialize(String[] args, DSpaceRunnableHandler dSpaceRunnableHandler, + EPerson currentUser) throws ParseException { + if (currentUser != null) { + this.setEpersonIdentifier(currentUser.getID()); + } + this.setHandler(dSpaceRunnableHandler); + this.parse(args); } /** @@ -80,18 +82,16 @@ public abstract class DSpaceRunnable implements Runnable { * @throws ParseException If something goes wrong */ private void parse(String[] args) throws ParseException { - commandLine = new DefaultParser().parse(getOptions(), args); + commandLine = new DefaultParser().parse(getScriptConfiguration().getOptions(), args); setup(); } /** - * This method will call upon the {@link DSpaceRunnableHandler#printHelp(Options, String)} method with the script's - * options and name + * 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 void printHelp() { - handler.printHelp(options, name); - } - + public abstract void setup() throws ParseException; /** * This is the run() method from the Runnable interface that we implement. This method will handle the running @@ -108,22 +108,6 @@ public abstract class DSpaceRunnable implements Runnable { } } - 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 @@ -132,25 +116,46 @@ public abstract class DSpaceRunnable implements Runnable { 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 + * This method will call upon the {@link DSpaceRunnableHandler#printHelp(Options, String)} method with the script's + * options and name */ - public abstract void setup() throws ParseException; + public void printHelp() { + handler.printHelp(getScriptConfiguration().getOptions(), getScriptConfiguration().getName()); + } /** - * 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 + * This method will traverse all the options and it'll grab options defined as an InputStream type to then save + * the filename specified by that option in a list of Strings that'll be returned in the end + * @return The list of Strings representing filenames from the options given to the script */ - 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); + public List getFileNamesFromInputStreamOptions() { + List fileNames = new LinkedList<>(); + + for (Option option : getScriptConfiguration().getOptions().getOptions()) { + if (option.getType() == InputStream.class && + StringUtils.isNotBlank(commandLine.getOptionValue(option.getOpt()))) { + fileNames.add(commandLine.getOptionValue(option.getOpt())); + } } - return false; + + return fileNames; + } + + /** + * Generic getter for the epersonIdentifier + * This EPerson identifier variable is the uuid of the eperson that's running the script + * @return the epersonIdentifier value of this DSpaceRunnable + */ + public UUID getEpersonIdentifier() { + return epersonIdentifier; + } + + /** + * Generic setter for the epersonIdentifier + * This EPerson identifier variable is the uuid of the eperson that's running the script + * @param epersonIdentifier The epersonIdentifier to be set on this DSpaceRunnable + */ + public void setEpersonIdentifier(UUID epersonIdentifier) { + this.epersonIdentifier = epersonIdentifier; } } diff --git a/dspace-api/src/main/java/org/dspace/scripts/Process.java b/dspace-api/src/main/java/org/dspace/scripts/Process.java index bc9204d429..c58669e6d9 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/Process.java +++ b/dspace-api/src/main/java/org/dspace/scripts/Process.java @@ -8,6 +8,7 @@ package org.dspace.scripts; import java.util.Date; +import java.util.LinkedList; import java.util.List; import javax.persistence.Column; import javax.persistence.Entity; @@ -80,6 +81,8 @@ public class Process implements ReloadableEntity { @Temporal(TemporalType.TIMESTAMP) private Date creationTime; + public static final String BITSTREAM_TYPE_METADATAFIELD = "dspace.process.filetype"; + protected Process() { } @@ -174,6 +177,9 @@ public class Process implements ReloadableEntity { * @return The Bitstreams that are used or created by the process */ public List getBitstreams() { + if (bitstreams == null) { + bitstreams = new LinkedList<>(); + } return bitstreams; } diff --git a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java index cb5a5c9944..1cdf3505db 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/scripts/ProcessServiceImpl.java @@ -7,18 +7,33 @@ */ package org.dspace.scripts; +import java.io.IOException; +import java.io.InputStream; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.regex.Pattern; +import org.apache.commons.collections4.ListUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Bitstream; +import org.dspace.content.Item; +import org.dspace.content.MetadataField; +import org.dspace.content.MetadataValue; import org.dspace.content.ProcessStatus; import org.dspace.content.dao.ProcessDAO; +import org.dspace.content.service.BitstreamFormatService; +import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.MetadataFieldService; +import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.LogManager; import org.dspace.eperson.EPerson; @@ -35,6 +50,18 @@ public class ProcessServiceImpl implements ProcessService { @Autowired private ProcessDAO processDAO; + @Autowired + private BitstreamService bitstreamService; + + @Autowired + private BitstreamFormatService bitstreamFormatService; + + @Autowired + private AuthorizeService authorizeService; + + @Autowired + private MetadataFieldService metadataFieldService; + @Override public Process create(Context context, EPerson ePerson, String scriptName, List parameters) throws SQLException { @@ -113,11 +140,35 @@ public class ProcessServiceImpl implements ProcessService { } @Override - public void delete(Context context, Process process) throws SQLException { + public void appendFile(Context context, Process process, InputStream is, String type, String fileName) + throws IOException, SQLException, AuthorizeException { + Bitstream bitstream = bitstreamService.create(context, is); + if (getBitstream(context, process, type) != null) { + throw new IllegalArgumentException("Cannot create another file of type: " + type + " for this process" + + " with id: " + process.getID()); + } + bitstream.setName(context, fileName); + bitstreamService.setFormat(context, bitstream, bitstreamFormatService.guessFormat(context, bitstream)); + MetadataField dspaceProcessFileTypeField = metadataFieldService + .findByString(context, Process.BITSTREAM_TYPE_METADATAFIELD, '.'); + bitstreamService.addMetadata(context, bitstream, dspaceProcessFileTypeField, null, type); + authorizeService.addPolicy(context, bitstream, Constants.READ, context.getCurrentUser()); + authorizeService.addPolicy(context, bitstream, Constants.WRITE, context.getCurrentUser()); + authorizeService.addPolicy(context, bitstream, Constants.DELETE, context.getCurrentUser()); + bitstreamService.update(context, bitstream); + process.addBitstream(bitstream); + update(context, process); + } + + @Override + public void delete(Context context, Process process) throws SQLException, IOException, AuthorizeException { + + for (Bitstream bitstream : ListUtils.emptyIfNull(process.getBitstreams())) { + bitstreamService.delete(context, bitstream); + } processDAO.delete(context, process); log.info(LogManager.getHeader(context, "process_delete", "Process with ID " + process.getID() + " and name " + process.getName() + " has been deleted")); - } @Override @@ -141,8 +192,57 @@ public class ProcessServiceImpl implements ProcessService { return parameterList; } + @Override + public Bitstream getBitstreamByName(Context context, Process process, String bitstreamName) { + for (Bitstream bitstream : getBitstreams(context, process)) { + if (StringUtils.equals(bitstream.getName(), bitstreamName)) { + return bitstream; + } + } + + return null; + } + + @Override + public Bitstream getBitstream(Context context, Process process, String type) { + List allBitstreams = process.getBitstreams(); + + if (type == null) { + return null; + } else { + if (allBitstreams != null) { + for (Bitstream bitstream : allBitstreams) { + if (StringUtils.equals(bitstreamService.getMetadata(bitstream, + Process.BITSTREAM_TYPE_METADATAFIELD), type)) { + return bitstream; + } + } + } + } + return null; + } + + @Override + public List getBitstreams(Context context, Process process) { + return process.getBitstreams(); + } + public int countTotal(Context context) throws SQLException { return processDAO.countRows(context); } + @Override + public List getFileTypesForProcessBitstreams(Context context, Process process) { + List list = getBitstreams(context, process); + Set fileTypesSet = new HashSet<>(); + for (Bitstream bitstream : list) { + List metadata = bitstreamService.getMetadata(bitstream, + Process.BITSTREAM_TYPE_METADATAFIELD, Item.ANY); + if (metadata != null && !metadata.isEmpty()) { + fileTypesSet.add(metadata.get(0).getValue()); + } + } + return new ArrayList<>(fileTypesSet); + } + } diff --git a/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java b/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java index e2a6acf3a8..4eb7cdbbc1 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/scripts/ScriptServiceImpl.java @@ -7,33 +7,46 @@ */ package org.dspace.scripts; +import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; import org.dspace.core.Context; +import org.dspace.kernel.ServiceManager; +import org.dspace.scripts.configuration.ScriptConfiguration; import org.dspace.scripts.service.ScriptService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** * The implementation for the {@link ScriptService} */ public class ScriptServiceImpl implements ScriptService { + private static final Logger log = LoggerFactory.getLogger(ScriptServiceImpl.class); @Autowired - private List dSpaceRunnables; + private ServiceManager serviceManager; @Override - public DSpaceRunnable getScriptForName(String name) { - return dSpaceRunnables.stream() - .filter(dSpaceRunnable -> StringUtils.equalsIgnoreCase(dSpaceRunnable.getName(), name)) - .findFirst() - .orElse(null); + public ScriptConfiguration getScriptConfiguration(String name) { + return serviceManager.getServiceByName(name, ScriptConfiguration.class); } @Override - public List getDSpaceRunnables(Context context) { - return dSpaceRunnables.stream().filter( - dSpaceRunnable -> dSpaceRunnable.isAllowedToExecute(context)).collect(Collectors.toList()); + public List getScriptConfigurations(Context context) { + return serviceManager.getServicesByType(ScriptConfiguration.class).stream().filter( + scriptConfiguration -> scriptConfiguration.isAllowedToExecute(context)).collect(Collectors.toList()); + } + + @Override + public DSpaceRunnable createDSpaceRunnableForScriptConfiguration(ScriptConfiguration scriptToExecute) + throws IllegalAccessException, InstantiationException { + try { + return (DSpaceRunnable) scriptToExecute.getDspaceRunnableClass().getDeclaredConstructor().newInstance(); + } catch (InvocationTargetException | NoSuchMethodException e) { + log.error(e.getMessage(), e); + throw new RuntimeException(e); + } } } diff --git a/dspace-api/src/main/java/org/dspace/scripts/configuration/ScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/scripts/configuration/ScriptConfiguration.java new file mode 100644 index 0000000000..4b15c22f44 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/scripts/configuration/ScriptConfiguration.java @@ -0,0 +1,92 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.scripts.configuration; + +import org.apache.commons.cli.Options; +import org.dspace.core.Context; +import org.dspace.scripts.DSpaceRunnable; +import org.springframework.beans.factory.BeanNameAware; + +/** + * This class represents an Abstract class that a ScriptConfiguration can inherit to further implement this + * and represent a script's configuration + */ +public abstract class ScriptConfiguration implements BeanNameAware { + + /** + * The possible options for this script + */ + protected Options options; + + private String description; + + private String name; + + /** + * Generic getter for the description + * @return the description value of this ScriptConfiguration + */ + public String getDescription() { + return description; + } + + /** + * Generic setter for the description + * @param description The description to be set on this ScriptConfiguration + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * Generic getter for the name + * @return the name value of this ScriptConfiguration + */ + public String getName() { + return name; + } + + /** + * Generic setter for the name + * @param name The name to be set on this ScriptConfiguration + */ + public void setName(String name) { + this.name = name; + } + + /** + * Generic getter for the dspaceRunnableClass + * @return the dspaceRunnableClass value of this ScriptConfiguration + */ + public abstract Class getDspaceRunnableClass(); + + /** + * Generic setter for the dspaceRunnableClass + * @param dspaceRunnableClass The dspaceRunnableClass to be set on this IndexDiscoveryScriptConfiguration + */ + public abstract void setDspaceRunnableClass(Class dspaceRunnableClass); + /** + * 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 abstract boolean isAllowedToExecute(Context context); + + /** + * The getter for the options of the Script + * @return the options value of this ScriptConfiguration + */ + public abstract Options getOptions(); + + @Override + public void setBeanName(String beanName) { + this.name = beanName; + } +} 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 index 01ca2fafd9..078ba6bfa2 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/handler/DSpaceRunnableHandler.java +++ b/dspace-api/src/main/java/org/dspace/scripts/handler/DSpaceRunnableHandler.java @@ -7,9 +7,14 @@ */ package org.dspace.scripts.handler; +import java.io.IOException; +import java.io.InputStream; import java.sql.SQLException; +import java.util.Optional; import org.apache.commons.cli.Options; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; /** * This is an interface meant to be implemented by any DSpaceRunnableHandler to specify specific execution methods @@ -78,4 +83,28 @@ public interface DSpaceRunnableHandler { * @param name The name of the script */ public void printHelp(Options options, String name); + + /** + * This method will grab the InputStream for the file defined by the given file name. The exact implementation will + * differ based on whether it's a REST call or CommandLine call. The REST Call will look for Bitstreams in the + * Database whereas the CommandLine call will look on the filesystem + * @param context The relevant DSpace context + * @param fileName The filename for the file that holds the InputStream + * @return The InputStream for the file defined by the given file name + * @throws IOException If something goes wrong + * @throws AuthorizeException If something goes wrong + */ + public Optional getFileStream(Context context, String fileName) throws IOException, AuthorizeException; + + /** + * This method will write the InputStream to either a file on the filesystem or a bitstream in the database + * depending on whether it's coming from a CommandLine call or REST call respectively + * @param context The relevant DSpace context + * @param fileName The filename + * @param inputStream The inputstream to be written + * @param type The type of the file + * @throws IOException If something goes wrong + */ + public void writeFilestream(Context context, String fileName, InputStream inputStream, String type) + throws IOException, SQLException, AuthorizeException; } 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 index 97925c1843..6775b9a455 100644 --- 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 @@ -7,9 +7,16 @@ */ package org.dspace.scripts.handler.impl; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Optional; + import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Options; +import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.Logger; +import org.dspace.core.Context; import org.dspace.scripts.handler.DSpaceRunnableHandler; /** @@ -84,4 +91,20 @@ public class CommandLineDSpaceRunnableHandler implements DSpaceRunnableHandler { formatter.printHelp(name, options); } } + + @Override + public Optional getFileStream(Context context, String fileName) throws IOException { + File file = new File(fileName); + if (!(file.exists() && file.isFile())) { + return Optional.empty(); + } + return Optional.of(FileUtils.openInputStream(file)); + } + + @Override + public void writeFilestream(Context context, String fileName, InputStream inputStream, String type) + throws IOException { + File file = new File(fileName); + FileUtils.copyInputStreamToFile(inputStream, file); + } } 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 index e277ab32f4..80a6ec932b 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java +++ b/dspace-api/src/main/java/org/dspace/scripts/service/ProcessService.java @@ -7,9 +7,13 @@ */ package org.dspace.scripts.service; +import java.io.IOException; +import java.io.InputStream; import java.sql.SQLException; import java.util.List; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Bitstream; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.scripts.DSpaceCommandLineParameter; @@ -104,13 +108,28 @@ public interface ProcessService { */ public void complete(Context context, Process process) throws SQLException; + /** + * The method will create a bitstream from the given inputstream with the given type as metadata and given name + * as name and attach it to the given process + * @param context The relevant DSpace context + * @param process The process for which the bitstream will be made + * @param is The inputstream for the bitstream + * @param type The type of the bitstream + * @param fileName The name of the bitstream + * @throws IOException If something goes wrong + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong + */ + public void appendFile(Context context, Process process, InputStream is, String type, String fileName) + throws IOException, SQLException, AuthorizeException; + /** * 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; + public void delete(Context context, Process process) throws SQLException, IOException, AuthorizeException; /** * This method will be used to update the given Process object in the database @@ -128,6 +147,32 @@ public interface ProcessService { */ public List getParameters(Process process); + /** + * This method will return the Bitstream that matches the given name for the given Process + * @param context The relevant DSpace context + * @param process The process that should hold the requested Bitstream + * @param bitstreamName The name of the requested Bitstream + * @return The Bitstream from the given Process that matches the given bitstream name + */ + public Bitstream getBitstreamByName(Context context, Process process, String bitstreamName); + + /** + * This method will return the Bitstream for a given process with a given type + * @param context The relevant DSpace context + * @param process The process that holds the Bitstreams to be searched in + * @param type The type that the Bitstream must have + * @return The Bitstream of the given type for the given Process + */ + public Bitstream getBitstream(Context context, Process process, String type); + + /** + * This method will return all the Bitstreams for a given process + * @param context The relevant DSpace context + * @param process The process that holds the Bitstreams to be searched in + * @return The list of Bitstreams + */ + public List getBitstreams(Context context, Process process); + /** * Returns the total amount of Process objects in the dataase * @param context The relevant DSpace context @@ -136,4 +181,12 @@ public interface ProcessService { */ int countTotal(Context context) throws SQLException; + /** + * This will return a list of Strings where each String represents the type of a Bitstream in the Process given + * @param context The DSpace context + * @param process The Process object that we'll use to find the bitstreams + * @return A list of Strings where each String represents a fileType that is in the Process + */ + public List getFileTypesForProcessBitstreams(Context context, Process process); + } 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 index fc680bd612..3716123822 100644 --- a/dspace-api/src/main/java/org/dspace/scripts/service/ScriptService.java +++ b/dspace-api/src/main/java/org/dspace/scripts/service/ScriptService.java @@ -11,6 +11,7 @@ import java.util.List; import org.dspace.core.Context; import org.dspace.scripts.DSpaceRunnable; +import org.dspace.scripts.configuration.ScriptConfiguration; /** * This service will deal with logic to handle DSpaceRunnable objects @@ -18,16 +19,29 @@ import org.dspace.scripts.DSpaceRunnable; public interface ScriptService { /** - * This method will return the DSpaceRunnable that has the name that's equal to the name given in the parameters + * This method will return the ScriptConfiguration 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 + * @return The matching ScriptConfiguration */ - DSpaceRunnable getScriptForName(String name); + ScriptConfiguration getScriptConfiguration(String name); /** - * This method will return a list of DSpaceRunnable objects for which the given Context is authorized to use them + * This method will return a list of ScriptConfiguration objects for which the given Context is authorized * @param context The relevant DSpace context - * @return The list of accessible DSpaceRunnable scripts for this context + * @return The list of accessible ScriptConfiguration scripts for this context */ - List getDSpaceRunnables(Context context); + List getScriptConfigurations(Context context); + + /** + * This method will create a new instance of the DSpaceRunnable that's linked with this Scriptconfiguration + * It'll grab the DSpaceRunnable class from the ScriptConfiguration's variables and create a new instance of it + * to return + * @param scriptToExecute The relevant ScriptConfiguration + * @return The new instance of the DSpaceRunnable class + * @throws IllegalAccessException If something goes wrong + * @throws InstantiationException If something goes wrong + */ + DSpaceRunnable createDSpaceRunnableForScriptConfiguration(ScriptConfiguration scriptToExecute) + throws IllegalAccessException, InstantiationException; } diff --git a/dspace-api/src/test/data/dspaceFolder/assetstore/testImport.csv b/dspace-api/src/test/data/dspaceFolder/assetstore/testImport.csv new file mode 100644 index 0000000000..cb658de4ed --- /dev/null +++ b/dspace-api/src/test/data/dspaceFolder/assetstore/testImport.csv @@ -0,0 +1,2 @@ +id,collection,dc.contributor.author ++,"123456789/2","Donald, SmithImported" diff --git a/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml index de19ef7287..d78b14c437 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/item-submission.xml @@ -82,9 +82,9 @@ - + submit.progressbar.CClicense + org.dspace.app.rest.submit.step.CCLicenseStep + cclicense - + diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index 3c4b4a839d..51ce1a0165 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -108,3 +108,15 @@ plugin.sequence.java.util.Collection = \ java.util.LinkedList, \ java.util.Stack, \ java.util.TreeSet + +########################################### +# PROPERTIES USED TO TEST CONFIGURATION # +# PROPERTY EXPOSURE VIA REST # +########################################### +rest.properties.exposed = configuration.exposed.single.value +rest.properties.exposed = configuration.exposed.array.value +rest.properties.exposed = configuration.not.existing + +configuration.not.exposed = secret_value +configuration.exposed.single.value = public_value +configuration.exposed.array.value = public_value_1, public_value_2 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 index b28d45ec18..30fb69a3c4 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml @@ -4,13 +4,23 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> - - + + - - + + + + + + + + + + + + diff --git a/dspace-api/src/test/java/org/dspace/app/bulkedit/DSpaceCSVTest.java b/dspace-api/src/test/java/org/dspace/app/bulkedit/DSpaceCSVTest.java index 1ddba1a011..9cb664fb78 100644 --- a/dspace-api/src/test/java/org/dspace/app/bulkedit/DSpaceCSVTest.java +++ b/dspace-api/src/test/java/org/dspace/app/bulkedit/DSpaceCSVTest.java @@ -18,6 +18,7 @@ import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.List; +import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.Logger; import org.dspace.AbstractUnitTest; import org.junit.Test; @@ -67,7 +68,7 @@ public class DSpaceCSVTest extends AbstractUnitTest { out = null; // Test the CSV parsing was OK - DSpaceCSV dcsv = new DSpaceCSV(new File(filename), context); + DSpaceCSV dcsv = new DSpaceCSV(FileUtils.openInputStream(new File(filename)), context); String[] lines = dcsv.getCSVLinesAsStringArray(); assertThat("testDSpaceCSV Good CSV", lines.length, equalTo(8)); @@ -96,7 +97,7 @@ public class DSpaceCSVTest extends AbstractUnitTest { // Test the CSV parsing was OK try { - dcsv = new DSpaceCSV(new File(filename), context); + dcsv = new DSpaceCSV(FileUtils.openInputStream(new File(filename)), context); lines = dcsv.getCSVLinesAsStringArray(); fail("An exception should have been thrown due to bad CSV"); @@ -124,7 +125,7 @@ public class DSpaceCSVTest extends AbstractUnitTest { // Test the CSV parsing was OK try { - dcsv = new DSpaceCSV(new File(filename), context); + dcsv = new DSpaceCSV(FileUtils.openInputStream(new File(filename)), context); lines = dcsv.getCSVLinesAsStringArray(); fail("An exception should have been thrown due to bad CSV"); diff --git a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportTest.java b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportTest.java new file mode 100644 index 0000000000..9594e2a2b1 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataExportTest.java @@ -0,0 +1,71 @@ +/** + * 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.bulkedit; + +import static junit.framework.TestCase.assertTrue; + +import java.io.File; +import java.io.FileInputStream; +import java.nio.charset.StandardCharsets; + +import org.apache.commons.io.IOUtils; +import org.dspace.AbstractIntegrationTest; +import org.dspace.app.launcher.ScriptLauncher; +import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.CollectionService; +import org.dspace.content.service.CommunityService; +import org.dspace.content.service.InstallItemService; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.junit.Test; + +public class MetadataExportTest extends AbstractIntegrationTest { + + private ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + private CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); + private CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); + private WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); + private InstallItemService installItemService = ContentServiceFactory.getInstance().getInstallItemService(); + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + + @Test + public void metadataExportToCsvTest() throws Exception { + context.turnOffAuthorisationSystem(); + Community community = communityService.create(null, context); + Collection collection = collectionService.create(context, community); + WorkspaceItem wi = workspaceItemService.create(context, collection, true); + Item item = wi.getItem(); + itemService.addMetadata(context, item, "dc", "contributor", "author", null, "Donald, Smith"); + item = installItemService.installItem(context, wi); + context.restoreAuthSystemState(); + String fileLocation = configurationService.getProperty("dspace.dir") + testProps.get("test.exportcsv") + .toString(); + + String[] args = new String[] {"metadata-export", "-i", String.valueOf(item.getHandle()), "-f", fileLocation}; + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + File file = new File(fileLocation); + String fileContent = IOUtils.toString(new FileInputStream(file), StandardCharsets.UTF_8); + assertTrue(fileContent.contains("Donald, Smith")); + assertTrue(fileContent.contains(String.valueOf(item.getID()))); + + context.turnOffAuthorisationSystem(); + itemService.delete(context, itemService.find(context, item.getID())); + collectionService.delete(context, collectionService.find(context, collection.getID())); + communityService.delete(context, communityService.find(context, community.getID())); + context.restoreAuthSystemState(); + } +} diff --git a/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportTest.java b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportTest.java new file mode 100644 index 0000000000..c0eb2789bc --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/bulkedit/MetadataImportTest.java @@ -0,0 +1,60 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.bulkedit; + +import static junit.framework.TestCase.assertTrue; + +import java.io.File; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.AbstractIntegrationTest; +import org.dspace.app.launcher.ScriptLauncher; +import org.dspace.app.scripts.handler.impl.TestDSpaceRunnableHandler; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.CollectionService; +import org.dspace.content.service.CommunityService; +import org.dspace.content.service.ItemService; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.junit.Test; + +public class MetadataImportTest extends AbstractIntegrationTest { + + private ItemService itemService = ContentServiceFactory.getInstance().getItemService(); + private CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService(); + private CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService(); + private ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); + + @Test + public void metadataImportTest() throws Exception { + context.turnOffAuthorisationSystem(); + Community community = communityService.create(null, context); + Collection collection = collectionService.create(context, community); + context.restoreAuthSystemState(); + + String fileLocation = new File(testProps.get("test.importcsv").toString()).getAbsolutePath(); + String[] args = new String[] {"metadata-import", "-f", fileLocation, "-e", eperson.getEmail(), "-s"}; + TestDSpaceRunnableHandler testDSpaceRunnableHandler = new TestDSpaceRunnableHandler(); + + ScriptLauncher.handleScript(args, ScriptLauncher.getConfig(kernelImpl), testDSpaceRunnableHandler, kernelImpl); + Item importedItem = itemService.findAll(context).next(); + assertTrue( + StringUtils.equals( + itemService.getMetadata(importedItem, "dc", "contributor", "author", Item.ANY).get(0).getValue(), + "Donald, SmithImported")); + + context.turnOffAuthorisationSystem(); + itemService.delete(context, itemService.find(context, importedItem.getID())); + collectionService.delete(context, collectionService.find(context, collection.getID())); + communityService.delete(context, communityService.find(context, community.getID())); + context.restoreAuthSystemState(); + } +} diff --git a/dspace-api/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java b/dspace-api/src/test/java/org/dspace/app/scripts/handler/impl/TestDSpaceRunnableHandler.java new file mode 100644 index 0000000000..1b5b3fa7ac --- /dev/null +++ b/dspace-api/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; + } +} diff --git a/dspace-api/src/test/java/org/dspace/license/MockCCLicenseConnectorServiceImpl.java b/dspace-api/src/test/java/org/dspace/license/MockCCLicenseConnectorServiceImpl.java new file mode 100644 index 0000000000..bb443ab4a4 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/license/MockCCLicenseConnectorServiceImpl.java @@ -0,0 +1,127 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.license; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.jdom.Document; +import org.jdom.JDOMException; + +/** + * Mock implementation for the Creative commons license connector service. + * This class will return a structure of CC Licenses similar to the CC License API but without having to contact it + */ +public class MockCCLicenseConnectorServiceImpl extends CCLicenseConnectorServiceImpl { + + /** + * Retrieves mock CC Licenses for the provided language + * @param language - the language + * @return a map of mocked licenses with the id and the license + */ + public Map retrieveLicenses(String language) { + Map ccLicenses = new HashMap<>(); + CCLicense mockLicense1 = createMockLicense(1, new int[]{3, 2, 3}); + CCLicense mockLicense2 = createMockLicense(2, new int[]{2}); + CCLicense mockLicense3 = createMockLicense(3, new int[]{}); + + ccLicenses.put(mockLicense1.getLicenseId(), mockLicense1); + ccLicenses.put(mockLicense2.getLicenseId(), mockLicense2); + ccLicenses.put(mockLicense3.getLicenseId(), mockLicense3); + + return ccLicenses; + } + + private CCLicense createMockLicense(int count, int[] amountOfFieldsAndEnums) { + String licenseId = "license" + count; + String licenseName = "License " + count + " - Name"; + List mockLicenseFields = createMockLicenseFields(count, amountOfFieldsAndEnums); + return new CCLicense(licenseId, licenseName, mockLicenseFields); + } + + private List createMockLicenseFields(int count, int[] amountOfFieldsAndEnums) { + List ccLicenseFields = new LinkedList<>(); + for (int index = 0; index < amountOfFieldsAndEnums.length; index++) { + String licenseFieldId = "license" + count + "-field" + index; + String licenseFieldLabel = "License " + count + " - Field " + index + " - Label"; + String licenseFieldDescription = "License " + count + " - Field " + index + " - Description"; + List mockLicenseFields = createMockLicenseFields(count, + index, + amountOfFieldsAndEnums[index]); + ccLicenseFields.add(new CCLicenseField(licenseFieldId, + licenseFieldLabel, + licenseFieldDescription, + mockLicenseFields)); + + } + + return ccLicenseFields; + } + + private List createMockLicenseFields(int count, int index, int amountOfEnums) { + List ccLicenseFieldEnumList = new LinkedList<>(); + for (int i = 0; i < amountOfEnums; i++) { + String enumId = "license" + count + "-field" + index + "-enum" + i; + String enumLabel = "License " + count + " - Field " + index + " - Enum " + i + " - Label"; + String enumDescription = "License " + count + " - Field " + index + " - Enum " + i + " - " + + "Description"; + ccLicenseFieldEnumList.add(new CCLicenseFieldEnum(enumId, enumLabel, enumDescription)); + } + return ccLicenseFieldEnumList; + + } + + /** + * Retrieve a mock CC License URI + * + * @param licenseId - the ID of the license + * @param language - the language for which to retrieve the full answerMap + * @param answerMap - the answers to the different field questions + * @return the CC License URI + */ + public String retrieveRightsByQuestion(final String licenseId, + final String language, + final Map answerMap) { + + return "mock-license-uri"; + } + + /** + * Retrieve a mock license RDF document. + * When the uri contains "invalid", null will be returned to simulate that no document was found for the provided + * URI + * + * @param licenseURI - The license URI for which to retrieve the license RDF document + * @return a mock license RDF document or null when the URI contains invalid + * @throws IOException + */ + public Document retrieveLicenseRDFDoc(String licenseURI) throws IOException { + if (!StringUtils.contains(licenseURI, "invalid")) { + InputStream cclicense = null; + try { + cclicense = getClass().getResourceAsStream("cc-license-rdf.xml"); + + Document doc = parser.build(cclicense); + return doc; + } catch (JDOMException e) { + throw new RuntimeException(e); + } finally { + if (cclicense != null) { + cclicense.close(); + } + } + } + return null; + } + +} diff --git a/dspace-api/src/test/java/org/dspace/scripts/MockDSpaceRunnableScriptConfiguration.java b/dspace-api/src/test/java/org/dspace/scripts/MockDSpaceRunnableScriptConfiguration.java new file mode 100644 index 0000000000..1197370e32 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/scripts/MockDSpaceRunnableScriptConfiguration.java @@ -0,0 +1,68 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.scripts; + +import java.io.InputStream; +import java.sql.SQLException; + +import org.apache.commons.cli.Options; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.core.Context; +import org.dspace.scripts.configuration.ScriptConfiguration; +import org.dspace.scripts.impl.MockDSpaceRunnableScript; +import org.springframework.beans.factory.annotation.Autowired; + +public class MockDSpaceRunnableScriptConfiguration extends ScriptConfiguration { + + + @Autowired + private AuthorizeService authorizeService; + + private Class dspaceRunnableClass; + + @Override + public Class getDspaceRunnableClass() { + return dspaceRunnableClass; + } + + /** + * Generic setter for the dspaceRunnableClass + * @param dspaceRunnableClass The dspaceRunnableClass to be set on this MetadataExportScriptConfiguration + */ + @Override + public void setDspaceRunnableClass(Class dspaceRunnableClass) { + this.dspaceRunnableClass = dspaceRunnableClass; + } + + @Override + public boolean isAllowedToExecute(Context context) { + try { + return authorizeService.isAdmin(context); + } catch (SQLException e) { + throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); + } + } + + @Override + public Options getOptions() { + if (options == null) { + 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); + options.addOption("f", "file", true, "source file"); + options.getOption("f").setType(InputStream.class); + options.getOption("f").setRequired(false); + super.options = options; + } + return options; + } +} 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 index 75f723d64b..960927e90a 100644 --- a/dspace-api/src/test/java/org/dspace/scripts/impl/MockDSpaceRunnableScript.java +++ b/dspace-api/src/test/java/org/dspace/scripts/impl/MockDSpaceRunnableScript.java @@ -7,19 +7,20 @@ */ package org.dspace.scripts.impl; -import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.dspace.scripts.DSpaceRunnable; +import org.dspace.scripts.MockDSpaceRunnableScriptConfiguration; +import org.dspace.utils.DSpace; -public class MockDSpaceRunnableScript extends DSpaceRunnable { - - private MockDSpaceRunnableScript() { - Options options = constructOptions(); - this.options = options; +public class MockDSpaceRunnableScript extends DSpaceRunnable { + @Override + public void internalRun() throws Exception { } @Override - public void internalRun() throws Exception { + public MockDSpaceRunnableScriptConfiguration getScriptConfiguration() { + return new DSpace().getServiceManager() + .getServiceByName("mock-script", MockDSpaceRunnableScriptConfiguration.class); } @Override @@ -28,15 +29,4 @@ public class MockDSpaceRunnableScript extends DSpaceRunnable { 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-api/src/test/resources/test-config.properties b/dspace-api/src/test/resources/test-config.properties index 49aaa9bb10..66a29ab9a0 100644 --- a/dspace-api/src/test/resources/test-config.properties +++ b/dspace-api/src/test/resources/test-config.properties @@ -11,3 +11,5 @@ test.folder = ./target/testing/ # Path of the test bitstream (to use in BitstreamTest and elsewhere) test.bitstream = ./target/testing/dspace/assetstore/ConstitutionofIreland.pdf +test.exportcsv = ./target/testing/dspace/assetstore/test.csv +test.importcsv = ./target/testing/dspace/assetstore/testImport.csv diff --git a/dspace-rest/pom.xml b/dspace-rest/pom.xml index 8f69306085..1038617b49 100644 --- a/dspace-rest/pom.xml +++ b/dspace-rest/pom.xml @@ -19,7 +19,7 @@ ${basedir}/.. - 5.3.1.RELEASE + 5.3.3.RELEASE diff --git a/dspace-server-webapp/pom.xml b/dspace-server-webapp/pom.xml index 88e31751f3..d8cc6a7587 100644 --- a/dspace-server-webapp/pom.xml +++ b/dspace-server-webapp/pom.xml @@ -172,6 +172,21 @@ + + + com.mycila + license-maven-plugin + + + **/src/test/resources/** + **/src/test/data/** + + src/main/webapp/index.html + src/main/webapp/login.html + src/main/webapp/js/hal/** + + + @@ -242,6 +257,23 @@ ${spring-hal-browser.version} + + + + org.webjars.bowergithub.jquery + jquery-dist + 3.5.1 + + + + org.webjars.bowergithub.codeseven + toastr + 2.1.4 + + + org.springframework.boot diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java index c89dd77f4f..18d06c87e8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/Application.java @@ -32,6 +32,7 @@ import org.springframework.web.context.request.RequestContextListener; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** @@ -149,6 +150,18 @@ public class Application extends SpringBootServletInitializer { } } + /** + * Add a new ResourceHandler to allow us to use WebJars.org to pull in web dependencies + * dynamically for HAL Browser, and access them off the /webjars path. + * @param registry ResourceHandlerRegistry + */ + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry + .addResourceHandler("/webjars/**") + .addResourceLocations("/webjars/"); + } + @Override public void addArgumentResolvers(@NonNull List argumentResolvers) { argumentResolvers.add(new SearchFilterResolver()); 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 31d427254a..46aefbe69e 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 @@ -81,7 +81,7 @@ public class RelationshipTypeRestController { List list = relationshipTypeService.findByEntityType(context, entityType, -1, -1); Page relationshipTypeRestPage = converter - .toRestPage(list, pageable, list.size(), utils.obtainProjection()); + .toRestPage(list, pageable, utils.obtainProjection()); Page relationshipTypeResources = relationshipTypeRestPage .map(relationshipTypeRest -> new RelationshipTypeResource(relationshipTypeRest, utils)); 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 a1684d782e..b501ba4406 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 @@ -342,7 +342,15 @@ public class RestResourceController implements InitializingBean { return findRelEntryInternal(request, response, apiCategory, model, id, rel, relid, page, assembler); } - + @RequestMapping(method = RequestMethod.GET, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_DIGIT + + "/{rel}/{relid}") + public RepresentationModel findRel(HttpServletRequest request, HttpServletResponse response, + @PathVariable String apiCategory, + @PathVariable String model, @PathVariable Integer id, @PathVariable String rel, + @PathVariable String relid, + Pageable page, PagedResourcesAssembler assembler) throws Throwable { + return findRelEntryInternal(request, response, apiCategory, model, id.toString(), rel, relid, page, assembler); + } /** * Execute a POST request; * 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 index 122fa76ac0..5cc956c5b1 100644 --- 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 @@ -7,6 +7,8 @@ */ package org.dspace.app.rest; +import java.util.List; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.converter.ConverterService; @@ -14,6 +16,9 @@ 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.dspace.app.rest.utils.ContextUtil; +import org.dspace.core.Context; +import org.dspace.services.RequestService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.rest.webmvc.ControllerUtils; import org.springframework.hateoas.RepresentationModel; @@ -24,7 +29,9 @@ 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; /** * This controller adds additional subresource methods to allow connecting scripts with processes @@ -41,6 +48,9 @@ public class ScriptProcessesController { @Autowired private ScriptRestRepository scriptRestRepository; + @Autowired + private RequestService requestService; + /** * 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 @@ -50,13 +60,16 @@ public class ScriptProcessesController { */ @RequestMapping(method = RequestMethod.POST) @PreAuthorize("hasAuthority('ADMIN')") - public ResponseEntity> startProcess(@PathVariable(name = "name") String scriptName) + public ResponseEntity> startProcess(@PathVariable(name = "name") String scriptName, + @RequestParam(name = "file") List files) throws Exception { if (log.isTraceEnabled()) { log.trace("Starting Process for Script with name: " + scriptName); } - ProcessRest processRest = scriptRestRepository.startProcess(scriptName); + Context context = ContextUtil.obtainContext(requestService.getCurrentRequest().getServletRequest()); + ProcessRest processRest = scriptRestRepository.startProcess(context, scriptName, files); ProcessResource processResource = converter.toResource(processRest); + context.complete(); return ControllerUtils.toResponseEntity(HttpStatus.ACCEPTED, new HttpHeaders(), processResource); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/SubmissionCCLicenseUrlRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/SubmissionCCLicenseUrlRepository.java new file mode 100644 index 0000000000..957484319c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/SubmissionCCLicenseUrlRepository.java @@ -0,0 +1,140 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.ServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.model.SubmissionCCLicenseUrlRest; +import org.dspace.app.rest.model.wrapper.SubmissionCCLicenseUrl; +import org.dspace.app.rest.repository.DSpaceRestRepository; +import org.dspace.app.rest.utils.Utils; +import org.dspace.core.Context; +import org.dspace.license.service.CreativeCommonsService; +import org.dspace.services.RequestService; +import org.dspace.utils.DSpace; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.hateoas.Link; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * This Repository is responsible for handling the CC License URIs. + * It only supports a search method + */ + +@Component(SubmissionCCLicenseUrlRest.CATEGORY + "." + SubmissionCCLicenseUrlRest.NAME) +public class SubmissionCCLicenseUrlRepository extends DSpaceRestRepository + implements InitializingBean { + + @Autowired + protected Utils utils; + + @Autowired + protected CreativeCommonsService creativeCommonsService; + + @Autowired + protected ConverterService converter; + + protected RequestService requestService = new DSpace().getRequestService(); + + @Autowired + DiscoverableEndpointsService discoverableEndpointsService; + + /** + * Retrieves the CC License URI based on the license ID and answers in the field questions, provided as parameters + * to this request + * + * @return the CC License URI as a SubmissionCCLicenseUrlRest + */ + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @SearchRestMethod(name = "rightsByQuestions") + public SubmissionCCLicenseUrlRest findByRightsByQuestions() { + ServletRequest servletRequest = requestService.getCurrentRequest() + .getServletRequest(); + Map requestParameterMap = servletRequest + .getParameterMap(); + Map parameterMap = new HashMap<>(); + String licenseId = servletRequest.getParameter("license"); + if (StringUtils.isBlank(licenseId)) { + throw new DSpaceBadRequestException( + "A \"license\" parameter needs to be provided."); + } + + // Loop through parameters to find answer parameters, adding them to the parameterMap. Zero or more answers + // may exist, as some CC licenses do not require answers + for (String parameter : requestParameterMap.keySet()) { + if (StringUtils.startsWith(parameter, "answer_")) { + String field = StringUtils.substringAfter(parameter, "answer_"); + String answer = ""; + if (requestParameterMap.get(parameter).length > 0) { + answer = requestParameterMap.get(parameter)[0]; + } + parameterMap.put(field, answer); + } + } + + Map fullParamMap = creativeCommonsService.retrieveFullAnswerMap(licenseId, parameterMap); + if (fullParamMap == null) { + throw new ResourceNotFoundException("No CC License could be matched on the provided ID: " + licenseId); + } + boolean licenseContainsCorrectInfo = creativeCommonsService.verifyLicenseInformation(licenseId, fullParamMap); + if (!licenseContainsCorrectInfo) { + throw new DSpaceBadRequestException( + "The provided answers do not match the required fields for the provided license."); + } + + String licenseUri = creativeCommonsService.retrieveLicenseUri(licenseId, fullParamMap); + + SubmissionCCLicenseUrl submissionCCLicenseUrl = new SubmissionCCLicenseUrl(licenseUri, licenseUri); + if (StringUtils.isBlank(licenseUri)) { + throw new ResourceNotFoundException("No CC License URI could be found for ID: " + licenseId); + } + + return converter.toRest(submissionCCLicenseUrl, utils.obtainProjection()); + + } + + /** + * The findOne method is not supported in this repository + */ + @PreAuthorize("permitAll()") + public SubmissionCCLicenseUrlRest findOne(final Context context, final String s) { + throw new RepositoryMethodNotImplementedException(SubmissionCCLicenseUrlRest.NAME, "findOne"); + } + + /** + * The findAll method is not supported in this repository + */ + public Page findAll(final Context context, final Pageable pageable) { + throw new RepositoryMethodNotImplementedException(SubmissionCCLicenseUrlRest.NAME, "findAll"); + } + + public Class getDomainClass() { + return SubmissionCCLicenseUrlRest.class; + } + + @Override + public void afterPropertiesSet() { + discoverableEndpointsService.register(this, Arrays.asList( + new Link("/api/" + SubmissionCCLicenseUrlRest.CATEGORY + "/" + + SubmissionCCLicenseUrlRest.NAME + "/search", + SubmissionCCLicenseUrlRest.NAME + "-search"))); + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowDefinitionCollectionsLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowDefinitionCollectionsLinkRepository.java index 1f66c0928d..7ae5f5ecc0 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowDefinitionCollectionsLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowDefinitionCollectionsLinkRepository.java @@ -72,7 +72,7 @@ public class WorkflowDefinitionCollectionsLinkRepository extends AbstractDSpaceR collectionsMappedToWorkflow.addAll(xmlWorkflowFactory.getCollectionHandlesMappedToWorklow(context, workflowName)); Pageable pageable = optionalPageable != null ? optionalPageable : PageRequest.of(0, 20); - return converter.toRestPage(utils.getPage(collectionsMappedToWorkflow, pageable), + return converter.toRestPage(collectionsMappedToWorkflow, pageable, projection); } else { throw new ResourceNotFoundException("No workflow with name " + workflowName + " is configured"); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowDefinitionStepsLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowDefinitionStepsLinkRepository.java index 4fdc391641..24c82ee460 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowDefinitionStepsLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowDefinitionStepsLinkRepository.java @@ -55,7 +55,7 @@ public class WorkflowDefinitionStepsLinkRepository extends AbstractDSpaceRestRep try { List steps = xmlWorkflowFactory.getWorkflowByName(workflowName).getSteps(); Pageable pageable = optionalPageable != null ? optionalPageable : PageRequest.of(0, 20); - return converter.toRestPage(utils.getPage(steps, pageable), projection); + return converter.toRestPage(steps, pageable, projection); } catch (WorkflowConfigurationException e) { throw new ResourceNotFoundException("No workflow with name " + workflowName + " is configured"); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowStepActionsLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowStepActionsLinkRepository.java index 8ddab1381f..f2b6a423f8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowStepActionsLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/WorkflowStepActionsLinkRepository.java @@ -52,6 +52,6 @@ public class WorkflowStepActionsLinkRepository extends AbstractDSpaceRestReposit Projection projection) { List actions = xmlWorkflowFactory.getStepByName(workflowStepName).getActions(); Pageable pageable = optionalPageable != null ? optionalPageable : PageRequest.of(0, 20); - return converter.toRestPage(utils.getPage(actions, pageable), projection); + return converter.toRestPage(actions, pageable, projection); } } 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 index a9dc827b79..fc786bfc85 100644 --- 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 @@ -7,33 +7,44 @@ */ package org.dspace.app.rest.converter; +import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; import javax.annotation.PostConstruct; +import org.apache.commons.lang3.StringUtils; 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.BaseObjectRest; 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.repository.DSpaceRestRepository; +import org.dspace.app.rest.security.DSpacePermissionEvaluator; +import org.dspace.app.rest.security.WebSecurityExpressionEvaluator; import org.dspace.app.rest.utils.Utils; +import org.dspace.services.RequestService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.core.annotation.AnnotationUtils; 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.EntityModel; import org.springframework.hateoas.Link; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; @@ -64,6 +75,15 @@ public class ConverterService { @Autowired private List projections; + @Autowired + private DSpacePermissionEvaluator dSpacePermissionEvaluator; + + @Autowired + private WebSecurityExpressionEvaluator webSecurityExpressionEvaluator; + + @Autowired + private RequestService requestService; + /** * Converts the given model object to a rest object, using the appropriate {@link DSpaceConverter} and * the given projection. @@ -86,18 +106,74 @@ public class ConverterService { M transformedModel = projection.transformModel(modelObject); DSpaceConverter converter = requireConverter(modelObject.getClass()); R restObject = converter.convert(transformedModel, projection); + if (restObject instanceof BaseObjectRest) { + BaseObjectRest baseObjectRest = (BaseObjectRest) restObject; + // This section will verify whether the current user has permissions to retrieve the + // rest object. It'll only return the REST object if the permission is granted. + // If permission isn't granted, it'll return null + String preAuthorizeValue = getPreAuthorizeAnnotationForBaseObject(baseObjectRest); + if (!webSecurityExpressionEvaluator + .evaluate(preAuthorizeValue, requestService.getCurrentRequest().getHttpServletRequest(), + requestService.getCurrentRequest().getHttpServletResponse(), + String.valueOf(baseObjectRest.getId()))) { + log.debug("Access denied on " + restObject.getClass() + " with id: " + + ((BaseObjectRest) restObject).getId()); + return null; + } + } if (restObject instanceof RestModel) { return (R) projection.transformRest((RestModel) restObject); } return restObject; } + private String getPreAuthorizeAnnotationForBaseObject(BaseObjectRest restObject) { + Annotation preAuthorize = getAnnotationForRestObject(restObject); + if (preAuthorize == null) { + preAuthorize = getDefaultFindOnePreAuthorize(); + + } + return parseAnnotation(preAuthorize); + + } + + private String parseAnnotation(Annotation preAuthorize) { + if (preAuthorize != null) { + return (String) AnnotationUtils.getValue(preAuthorize); + } + return null; + } + + private Annotation getAnnotationForRestObject(BaseObjectRest restObject) { + BaseObjectRest baseObjectRest = restObject; + DSpaceRestRepository repositoryToUse = utils + .getResourceRepositoryByCategoryAndModel(baseObjectRest.getCategory(), baseObjectRest.getType()); + Annotation preAuthorize = null; + for (Method m : repositoryToUse.getClass().getMethods()) { + if (StringUtils.equalsIgnoreCase(m.getName(), "findOne")) { + preAuthorize = AnnotationUtils.findAnnotation(m, PreAuthorize.class); + } + } + return preAuthorize; + } + + private Annotation getDefaultFindOnePreAuthorize() { + for (Method m : DSpaceRestRepository.class.getMethods()) { + if (StringUtils.equalsIgnoreCase(m.getName(), "findOne")) { + Annotation annotation = AnnotationUtils.findAnnotation(m, PreAuthorize.class); + if (annotation != null) { + return annotation; + } + } + } + return null; + } + /** * 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. @@ -105,25 +181,48 @@ public class ConverterService { * @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)); + public Page toRestPage(List modelObjects, Pageable pageable, Projection projection) { + List transformedList = new LinkedList<>(); + for (M modelObject : modelObjects) { + R transformedObject = toRest(modelObject, projection); + if (transformedObject != null) { + transformedList.add(transformedObject); + } + } + if (pageable == null) { + pageable = utils.getPageable(pageable); + } + return utils.getPage(transformedList, pageable); } /** - * Converts a list of model objects to a page of rest objects using the given {@link Projection}. - * - * @param modelObjects the page of model objects. + * Converts a list of ModelObjects to a page of Rest Objects using the given {@link Projection} + * This method differences in the sense that we define a total here instead of the size of the list because + * this method will be called if the list is limited through a DB call already and thus we need to give the + * total amount of records in the DB; not the size of the given list + * @param modelObjects the list of model objects. + * @param pageable the pageable. + * @param total The total amount of 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)); + public Page toRestPage(List modelObjects, Pageable pageable, long total, Projection projection) { + List transformedList = new LinkedList<>(); + for (M modelObject : modelObjects) { + R transformedObject = toRest(modelObject, projection); + if (transformedObject != null) { + transformedList.add(transformedObject); + } + } + if (pageable == null) { + pageable = utils.getPageable(pageable); + } + return new PageImpl(transformedList, pageable, total); } + /** * Gets the converter supporting the given class as input. * @@ -177,6 +276,9 @@ public class ConverterService { * @return the fully converted resource, with all automatic links and embeds applied. */ public T toResource(RestModel restObject, Link... oldLinks) { + if (restObject == null) { + return null; + } T halResource = getResource(restObject); if (restObject instanceof RestAddressableModel) { utils.embedOrLinkClassLevelRels(halResource, oldLinks); @@ -288,19 +390,19 @@ public class ConverterService { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); provider.addIncludeFilter(new AssignableTypeFilter(EntityModel.class)); Set beanDefinitions = provider.findCandidateComponents( - HALResource.class.getPackage().getName().replaceAll("\\.", "/")); + 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"); + .replaceAll("ResourceWrapper$", "RestWrapper") + .replaceAll("Resource$", "Rest"); String restClassName = RestModel.class.getPackage().getName() + "." + restClassSimpleName; try { Class restClass = - (Class) Class.forName(restClassName); + (Class) Class.forName(restClassName); Class> resourceClass = - (Class>) Class.forName(resourceClassName); + (Class>) Class.forName(resourceClassName); Constructor compatibleConstructor = null; for (Constructor constructor : resourceClass.getDeclaredConstructors()) { if (constructor.getParameterCount() == 2 && constructor.getParameterTypes()[1] == Utils.class) { @@ -314,11 +416,11 @@ public class ConverterService { resourceConstructors.put(restClass, compatibleConstructor); } else { log.warn("Skipping registration of resource class " + resourceClassName - + "; compatible constructor not found"); + + "; compatible constructor not found"); } } catch (ClassNotFoundException e) { log.warn("Skipping registration of resource class " + resourceClassName - + "; rest class not found: " + restClassName); + + "; rest class not found: " + restClassName); } } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RootConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RootConverter.java index b75f5f2751..c49d19842d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RootConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/RootConverter.java @@ -24,8 +24,8 @@ public class RootConverter { public RootRest convert() { RootRest rootRest = new RootRest(); rootRest.setDspaceName(configurationService.getProperty("dspace.name")); - rootRest.setDspaceURL(configurationService.getProperty("dspace.ui.url")); - rootRest.setDspaceRest(configurationService.getProperty("dspace.server.url")); + rootRest.setDspaceUI(configurationService.getProperty("dspace.ui.url")); + rootRest.setDspaceServer(configurationService.getProperty("dspace.server.url")); return rootRest; } } 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 index 379d651617..105975355b 100644 --- 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 @@ -15,7 +15,7 @@ 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.dspace.scripts.configuration.ScriptConfiguration; import org.springframework.stereotype.Component; /** @@ -23,18 +23,18 @@ import org.springframework.stereotype.Component; * of {@link ScriptRest} */ @Component -public class ScriptConverter implements DSpaceConverter { +public class ScriptConverter implements DSpaceConverter { @Override - public ScriptRest convert(DSpaceRunnable script, Projection projection) { + public ScriptRest convert(ScriptConfiguration scriptConfiguration, Projection projection) { ScriptRest scriptRest = new ScriptRest(); scriptRest.setProjection(projection); - scriptRest.setDescription(script.getDescription()); - scriptRest.setId(script.getName()); - scriptRest.setName(script.getName()); + scriptRest.setDescription(scriptConfiguration.getDescription()); + scriptRest.setId(scriptConfiguration.getName()); + scriptRest.setName(scriptConfiguration.getName()); List parameterRestList = new LinkedList<>(); - for (Option option : CollectionUtils.emptyIfNull(script.getOptions().getOptions())) { + for (Option option : CollectionUtils.emptyIfNull(scriptConfiguration.getOptions().getOptions())) { ParameterRest parameterRest = new ParameterRest(); parameterRest.setDescription(option.getDescription()); parameterRest.setName((option.getOpt() != null ? "-" + option.getOpt() : "--" + option.getLongOpt())); @@ -49,7 +49,7 @@ public class ScriptConverter implements DSpaceConverter getModelClass() { - return DSpaceRunnable.class; + public Class getModelClass() { + return ScriptConfiguration.class; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionCCLicenseConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionCCLicenseConverter.java new file mode 100644 index 0000000000..bf6b92a618 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionCCLicenseConverter.java @@ -0,0 +1,60 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.converter; + +import java.util.LinkedList; +import java.util.List; + +import org.dspace.app.rest.model.SubmissionCCLicenseFieldRest; +import org.dspace.app.rest.model.SubmissionCCLicenseRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.license.CCLicense; +import org.dspace.license.CCLicenseField; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * This converter is responsible for transforming the model representation of an CCLicense to the REST + * representation of an CCLicense and vice versa + **/ +@Component +public class SubmissionCCLicenseConverter implements DSpaceConverter { + + @Autowired + private ConverterService converter; + + /** + * Convert a CCLicense to its REST representation + * @param modelObject - the CCLicense to convert + * @param projection - the projection + * @return the corresponding SubmissionCCLicenseRest object + */ + @Override + public SubmissionCCLicenseRest convert(final CCLicense modelObject, final Projection projection) { + SubmissionCCLicenseRest submissionCCLicenseRest = new SubmissionCCLicenseRest(); + submissionCCLicenseRest.setProjection(projection); + submissionCCLicenseRest.setId(modelObject.getLicenseId()); + submissionCCLicenseRest.setName(modelObject.getLicenseName()); + + List ccLicenseFieldList = modelObject.getCcLicenseFieldList(); + List submissionCCLicenseFieldRests = new LinkedList<>(); + if (ccLicenseFieldList != null) { + for (CCLicenseField ccLicenseField : ccLicenseFieldList) { + submissionCCLicenseFieldRests.add(converter.toRest(ccLicenseField, projection)); + } + } + submissionCCLicenseRest.setFields(submissionCCLicenseFieldRests); + return submissionCCLicenseRest; + } + + @Override + public Class getModelClass() { + return CCLicense.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionCCLicenseFieldConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionCCLicenseFieldConverter.java new file mode 100644 index 0000000000..782056dc1c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionCCLicenseFieldConverter.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.converter; + +import java.util.LinkedList; +import java.util.List; + +import org.dspace.app.rest.model.SubmissionCCLicenseFieldEnumRest; +import org.dspace.app.rest.model.SubmissionCCLicenseFieldRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.license.CCLicenseField; +import org.dspace.license.CCLicenseFieldEnum; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * This converter is responsible for transforming the model representation of an CCLicenseField to the REST + * representation of an CCLicenseField and vice versa + * The CCLicenseField is a sub component of the CCLicense object + **/ +@Component +public class SubmissionCCLicenseFieldConverter + implements DSpaceConverter { + + @Autowired + private ConverterService converter; + + /** + * Convert a CCLicenseField to its REST representation + * @param modelObject - the CCLicenseField to convert + * @param projection - the projection + * @return the corresponding SubmissionCCLicenseFieldRest object + */ + @Override + public SubmissionCCLicenseFieldRest convert(final CCLicenseField modelObject, final Projection projection) { + SubmissionCCLicenseFieldRest submissionCCLicenseFieldRest = new SubmissionCCLicenseFieldRest(); + submissionCCLicenseFieldRest.setId(modelObject.getId()); + submissionCCLicenseFieldRest.setLabel(modelObject.getLabel()); + submissionCCLicenseFieldRest.setDescription(modelObject.getDescription()); + + List fieldEnum = modelObject.getFieldEnum(); + List submissionCCLicenseFieldEnumRests = new LinkedList<>(); + if (fieldEnum != null) { + for (CCLicenseFieldEnum ccLicenseFieldEnum : fieldEnum) { + submissionCCLicenseFieldEnumRests.add(converter.toRest(ccLicenseFieldEnum, projection)); + } + } + submissionCCLicenseFieldRest.setEnums(submissionCCLicenseFieldEnumRests); + return submissionCCLicenseFieldRest; + } + + @Override + public Class getModelClass() { + return CCLicenseField.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionCCLicenseFieldEnumConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionCCLicenseFieldEnumConverter.java new file mode 100644 index 0000000000..6c8993905f --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionCCLicenseFieldEnumConverter.java @@ -0,0 +1,46 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.converter; + +import org.dspace.app.rest.model.SubmissionCCLicenseFieldEnumRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.license.CCLicenseFieldEnum; +import org.springframework.stereotype.Component; + +/** + * This converter is responsible for transforming the model representation of an CCLicenseFieldEnum to the REST + * representation of an CCLicenseFieldEnum and vice versa + * The CCLicenseFieldEnum is a sub component of the CCLicenseField object + **/ +@Component +public class SubmissionCCLicenseFieldEnumConverter + implements DSpaceConverter { + + /** + * Convert a CCLicenseFieldEnum to its REST representation + * + * @param modelObject - the CCLicenseField to convert + * @param projection - the projection + * @return the corresponding SubmissionCCLicenseFieldEnumRest object + */ + @Override + public SubmissionCCLicenseFieldEnumRest convert(final CCLicenseFieldEnum modelObject, final Projection projection) { + SubmissionCCLicenseFieldEnumRest submissionCCLicenseFieldEnumRest = new SubmissionCCLicenseFieldEnumRest(); + submissionCCLicenseFieldEnumRest.setId(modelObject.getId()); + submissionCCLicenseFieldEnumRest.setLabel(modelObject.getLabel()); + submissionCCLicenseFieldEnumRest.setDescription(modelObject.getDescription()); + + return submissionCCLicenseFieldEnumRest; + } + + @Override + public Class getModelClass() { + return CCLicenseFieldEnum.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionCCLicenseUrlConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionCCLicenseUrlConverter.java new file mode 100644 index 0000000000..c5fd41fb7b --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionCCLicenseUrlConverter.java @@ -0,0 +1,44 @@ +/** + * 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.SubmissionCCLicenseUrlRest; +import org.dspace.app.rest.model.wrapper.SubmissionCCLicenseUrl; +import org.dspace.app.rest.projection.Projection; +import org.springframework.stereotype.Component; + + +/** + * This converter is responsible for transforming a Submission CC License Url String to the REST + * representation SubmissionCCLicenseUrlRest and vice versa + */ +@Component +public class SubmissionCCLicenseUrlConverter + implements DSpaceConverter { + + /** + * Convert a Submission CC License Url String to its REST representation + * @param modelObject - the CC License Url object to convert + * @param projection - the projection + * @return the corresponding SubmissionCCLicenseUrlRest object + */ + @Override + public SubmissionCCLicenseUrlRest convert(SubmissionCCLicenseUrl modelObject, Projection projection) { + SubmissionCCLicenseUrlRest submissionCCLicenseUrlRest = new SubmissionCCLicenseUrlRest(); + submissionCCLicenseUrlRest.setUrl(modelObject.getUrl()); + submissionCCLicenseUrlRest.setId(modelObject.getId()); + + return submissionCCLicenseUrlRest; + } + + @Override + public Class getModelClass() { + return SubmissionCCLicenseUrl.class; + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java index e0b3a86d18..d255b6fe27 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/exception/DSpaceApiExceptionControllerAdvice.java @@ -29,6 +29,7 @@ import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.context.request.WebRequest; +import org.springframework.web.multipart.MultipartException; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; /** @@ -54,8 +55,8 @@ public class DSpaceApiExceptionControllerAdvice extends ResponseEntityExceptionH } } - @ExceptionHandler(IllegalArgumentException.class) - protected void handleIllegalArgumentException(HttpServletRequest request, HttpServletResponse response, + @ExceptionHandler({IllegalArgumentException.class, MultipartException.class}) + protected void handleWrongRequestException(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { sendErrorResponse(request, response, ex, ex.getMessage(), HttpServletResponse.SC_BAD_REQUEST); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/RootHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/RootHalLinkFactory.java index 55751435d4..7ce95aa18c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/RootHalLinkFactory.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/RootHalLinkFactory.java @@ -30,7 +30,7 @@ public class RootHalLinkFactory extends HalLinkFactory This parameter should be of type {@link org.dspace.app.rest.model.hateoas.HALResource} + */ +public abstract class ProcessHalLinkFactory extends HalLinkFactory { +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/process/ProcessResourceHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/process/ProcessResourceHalLinkFactory.java index 8325080861..041c4c651b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/process/ProcessResourceHalLinkFactory.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/process/ProcessResourceHalLinkFactory.java @@ -10,7 +10,6 @@ package org.dspace.app.rest.link.process; import java.util.LinkedList; import org.dspace.app.rest.RestResourceController; -import org.dspace.app.rest.link.HalLinkFactory; import org.dspace.app.rest.model.hateoas.ProcessResource; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; @@ -19,25 +18,28 @@ import org.springframework.hateoas.Link; import org.springframework.stereotype.Component; /** - * This class will provide the ProcessResource with links + * This HalLinkFactory provides the {@link ProcessResource} with links */ @Component -public class ProcessResourceHalLinkFactory extends HalLinkFactory { +public class ProcessResourceHalLinkFactory extends ProcessHalLinkFactory { @Autowired private ConfigurationService configurationService; + @Override protected void addLinks(ProcessResource halResource, Pageable pageable, LinkedList list) throws Exception { - String dspaceRestUrl = configurationService.getProperty("dspace.server.url"); + String dspaceServerUrl = configurationService.getProperty("dspace.server.url"); list.add( - buildLink("script", dspaceRestUrl + "/api/system/scripts/" + halResource.getContent().getScriptName())); + buildLink("script", dspaceServerUrl + "/api/system/scripts/" + halResource.getContent().getScriptName())); } + @Override protected Class getControllerClass() { return RestResourceController.class; } + @Override protected Class getResourceClass() { return ProcessResource.class; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/process/SubmissionCCLicenseUrlResourceHalLinkFactory.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/process/SubmissionCCLicenseUrlResourceHalLinkFactory.java new file mode 100644 index 0000000000..07d5e46c61 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/link/process/SubmissionCCLicenseUrlResourceHalLinkFactory.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.link.process; + +import java.util.LinkedList; +import java.util.Map; + +import org.dspace.app.rest.RestResourceController; +import org.dspace.app.rest.link.HalLinkFactory; +import org.dspace.app.rest.model.SubmissionCCLicenseUrlRest; +import org.dspace.app.rest.model.hateoas.SubmissionCCLicenseUrlResource; +import org.dspace.services.RequestService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.hateoas.Link; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.web.util.UriComponentsBuilder; + +/** + * This class will provide the SubmissionCCLicenseUrlResource with links + */ +@Component +public class SubmissionCCLicenseUrlResourceHalLinkFactory + extends HalLinkFactory { + + @Autowired + RequestService requestService; + + /** + * Add a self link based on the search parameters + * + * @param halResource - The halResource + * @param pageable - The page information + * @param list - The list of present links + * @throws Exception + */ + @Override + protected void addLinks(SubmissionCCLicenseUrlResource halResource, final Pageable pageable, + LinkedList list) + throws Exception { + + halResource.removeLinks(); + Map parameterMap = requestService.getCurrentRequest().getHttpServletRequest() + .getParameterMap(); + + + UriComponentsBuilder uriComponentsBuilder = uriBuilder(getMethodOn().executeSearchMethods( + SubmissionCCLicenseUrlRest.CATEGORY, SubmissionCCLicenseUrlRest.PLURAL, "rightsByQuestions", null, null, + null, null, new LinkedMultiValueMap<>())); + for (String key : parameterMap.keySet()) { + uriComponentsBuilder.queryParam(key, parameterMap.get(key)); + } + + list.add(buildLink("self", uriComponentsBuilder.build().toUriString())); + } + + + @Override + protected Class getControllerClass() { + return RestResourceController.class; + } + + @Override + protected Class getResourceClass() { + return SubmissionCCLicenseUrlResource.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceEntryRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceEntryRest.java index aa5dfa8cf2..06af7e2227 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceEntryRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ExternalSourceEntryRest.java @@ -12,7 +12,7 @@ import org.dspace.app.rest.ExternalSourcesRestController; /** * This class serves as a REST representation for an entry of external data */ -public class ExternalSourceEntryRest extends BaseObjectRest { +public class ExternalSourceEntryRest extends RestAddressableModel { public static final String NAME = "externalSourceEntry"; public static final String PLURAL_NAME = "externalSourceEntries"; 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 index 53eb2033b9..473426e533 100644 --- 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 @@ -25,16 +25,16 @@ public class ParameterRest { */ private String type; - /** - * The long name of the parameter - */ - private String nameLong; - /** * Boolean indicating whether the parameter is mandatory or not */ private boolean mandatory; + /** + * The long name of the parameter + */ + private String nameLong; + public String getName() { return name; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessFileTypesRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessFileTypesRest.java new file mode 100644 index 0000000000..ecceea107e --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ProcessFileTypesRest.java @@ -0,0 +1,68 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model; + +import java.util.LinkedList; +import java.util.List; + +import org.dspace.app.rest.RestResourceController; + +/** + * This class provides a way to list the filetypes present in a given Process by showing them as a list of Strings + * It'll be used by {@link org.dspace.app.rest.repository.ProcessFileTypesLinkRepository} + */ +public class ProcessFileTypesRest extends BaseObjectRest { + + public static final String NAME = "filetypes"; + public static final String PLURAL_NAME = "filetypes"; + public static final String CATEGORY = RestAddressableModel.SYSTEM; + + private List values; + + /** + * Generic getter for the values + * @return the values value of this ProcessFileTypesRest + */ + public List getValues() { + return values; + } + + /** + * Generic setter for the values + * @param values The values to be set on this ProcessFileTypesRest + */ + public void setValues(List values) { + this.values = values; + } + + /** + * Adds a value to the list of FileType Strings + * @param value The value to be added + */ + public void addValue(String value) { + if (values == null) { + values = new LinkedList<>(); + } + values.add(value); + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return RestResourceController.class; + } + + @Override + public String getType() { + return NAME; + } +} 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 index 6d3ddfae43..399d880f3b 100644 --- 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 @@ -20,12 +20,23 @@ import org.dspace.scripts.Process; /** * This class serves as a REST representation for the {@link Process} class */ +@LinksRest(links = { + @LinkRest( + name = ProcessRest.FILES, + method = "getFilesFromProcess" + ), + @LinkRest( + name = ProcessRest.FILE_TYPES, + method = "getFileTypesFromProcess" + ) +}) 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 static final String FILES = "files"; + public static final String FILE_TYPES = "filetypes"; public String getCategory() { return CATEGORY; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PropertyRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PropertyRest.java new file mode 100644 index 0000000000..365a679019 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/PropertyRest.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.model; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.dspace.app.rest.RestResourceController; + +/** + * This class acts as the REST representation of a DSpace configuration property. + * This class acts as a data holder for the PropertyResource + */ +public class PropertyRest extends BaseObjectRest { + public static final String NAME = "property"; + public static final String CATEGORY = RestAddressableModel.CONFIGURATION; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getValues() { + return values; + } + + public void setValues(List values) { + this.values = values; + } + + public String name; + public List values; + + @Override + @JsonIgnore + public String getId() { + return this.name; + } + + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + public Class getController() { + return RestResourceController.class; + } + + @Override + public String getType() { + return NAME; + } +} 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 bb20ef9e43..3079f1a0c2 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 @@ -17,9 +17,9 @@ import org.dspace.app.rest.RootRestResourceController; public class RootRest extends RestAddressableModel { public static final String NAME = "root"; public static final String CATEGORY = RestModel.ROOT; - private String dspaceURL; + private String dspaceUI; private String dspaceName; - private String dspaceRest; + private String dspaceServer; public String getCategory() { return CATEGORY; @@ -33,13 +33,13 @@ public class RootRest extends RestAddressableModel { return RootRestResourceController.class; } - public String getDspaceURL() { + public String getDspaceUI() { - return dspaceURL; + return dspaceUI; } - public void setDspaceURL(String dspaceURL) { - this.dspaceURL = dspaceURL; + public void setDspaceUI(String dspaceUI) { + this.dspaceUI = dspaceUI; } public String getDspaceName() { @@ -50,12 +50,12 @@ public class RootRest extends RestAddressableModel { this.dspaceName = dspaceName; } - public String getDspaceRest() { - return dspaceRest; + public String getDspaceServer() { + return dspaceServer; } - public void setDspaceRest(String dspaceRest) { - this.dspaceRest = dspaceRest; + public void setDspaceServer(String dspaceServerURL) { + this.dspaceServer = dspaceServerURL; } @Override @@ -64,9 +64,9 @@ public class RootRest extends RestAddressableModel { new EqualsBuilder().append(this.getCategory(), ((RootRest) object).getCategory()) .append(this.getType(), ((RootRest) object).getType()) .append(this.getController(), ((RootRest) object).getController()) - .append(this.getDspaceURL(), ((RootRest) object).getDspaceURL()) + .append(this.getDspaceUI(), ((RootRest) object).getDspaceUI()) .append(this.getDspaceName(), ((RootRest) object).getDspaceName()) - .append(this.getDspaceRest(), ((RootRest) object).getDspaceRest()) + .append(this.getDspaceServer(), ((RootRest) object).getDspaceServer()) .isEquals()); } @@ -77,8 +77,8 @@ public class RootRest extends RestAddressableModel { .append(this.getType()) .append(this.getController()) .append(this.getDspaceName()) - .append(this.getDspaceURL()) - .append(this.getDspaceRest()) + .append(this.getDspaceUI()) + .append(this.getDspaceServer()) .toHashCode(); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseFieldEnumRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseFieldEnumRest.java new file mode 100644 index 0000000000..770eb25782 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseFieldEnumRest.java @@ -0,0 +1,44 @@ +/** + * 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 is the REST representation of the CCLicenseFieldEnum model object and acts as a data sub object + * for the SubmissionCCLicenseFieldRest class. + * Refer to {@link org.dspace.license.CCLicenseFieldEnum} for explanation of the properties + */ +public class SubmissionCCLicenseFieldEnumRest { + + private String id; + private String label; + private String description; + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + public String getLabel() { + return label; + } + + public void setLabel(final String label) { + this.label = label; + } + + public String getDescription() { + return description; + } + + public void setDescription(final String description) { + this.description = description; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseFieldRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseFieldRest.java new file mode 100644 index 0000000000..bcc90279dc --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseFieldRest.java @@ -0,0 +1,59 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model; + +import java.util.List; + +/** + * This class is the REST representation of the CCLicenseField model object and acts as a data sub object + * for the SubmissionCCLicenseRest class. + * Refer to {@link org.dspace.license.CCLicenseField} for explanation of the properties + */ +public class SubmissionCCLicenseFieldRest { + + private String id; + + private String label; + + private String description; + + private List enums; + + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + public String getLabel() { + return label; + } + + public void setLabel(final String label) { + this.label = label; + } + + public String getDescription() { + return description; + } + + public void setDescription(final String description) { + this.description = description; + } + + public List getEnums() { + return enums; + } + + public void setEnums(final List enums) { + this.enums = enums; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseRest.java new file mode 100644 index 0000000000..23589d5a46 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseRest.java @@ -0,0 +1,74 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.dspace.app.rest.RestResourceController; + +/** + * This class is the REST representation of the CCLicense model object and acts as a data object + * for the SubmissionCCLicenseResource class. + * Refer to {@link org.dspace.license.CCLicense} for explanation of the properties + */ +public class SubmissionCCLicenseRest extends BaseObjectRest { + public static final String NAME = "submissioncclicense"; + public static final String PLURAL = "submissioncclicenses"; + + public static final String CATEGORY = RestAddressableModel.CONFIGURATION; + + private String id; + + private String name; + + private List fields; + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public List getFields() { + return fields; + } + + public void setFields(final List fields) { + this.fields = fields; + } + + @JsonIgnore + @Override + public String getCategory() { + return CATEGORY; + } + + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getType() { + return NAME; + } + + @Override + @JsonIgnore + public Class getController() { + return RestResourceController.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseUrlRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseUrlRest.java new file mode 100644 index 0000000000..77263ba317 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionCCLicenseUrlRest.java @@ -0,0 +1,60 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.dspace.app.rest.RestResourceController; + +/** + * This class is the REST representation of the CCLicense URL String object and acts as a data object + * for the SubmissionCCLicenseUrlRest class. + */ +public class SubmissionCCLicenseUrlRest extends BaseObjectRest { + public static final String NAME = "submissioncclicenseUrl"; + public static final String PLURAL = "submissioncclicenseUrls"; + public static final String CATEGORY = RestAddressableModel.CONFIGURATION; + + + private String url; + + @JsonIgnore + @Override + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getUrl() { + return url; + } + + public void setUrl(final String url) { + this.url = url; + } + + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getType() { + return NAME; + } + + @Override + public String getCategory() { + return SubmissionCCLicenseUrlRest.CATEGORY; + } + + @Override + @JsonIgnore + public Class getController() { + return RestResourceController.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ProcessFileTypesResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ProcessFileTypesResource.java new file mode 100644 index 0000000000..75c26b95f5 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/ProcessFileTypesResource.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.ProcessFileTypesRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; + +/** + * Resource object for {@link ProcessFileTypesRest} + */ +@RelNameDSpaceResource(ProcessFileTypesRest.NAME) +public class ProcessFileTypesResource extends HALResource { + + public ProcessFileTypesResource(ProcessFileTypesRest content) { + super(content); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/PropertyResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/PropertyResource.java new file mode 100644 index 0000000000..96df54ce9a --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/PropertyResource.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.model.hateoas; + +import org.dspace.app.rest.model.PropertyRest; +import org.dspace.app.rest.utils.Utils; + +/** + * The purpose of this class is to wrap the information of the PropertyRest into a HAL resource + */ +public class PropertyResource extends DSpaceResource { + + public PropertyResource(PropertyRest data, Utils utils) { + super(data, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionCCLicenseResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionCCLicenseResource.java new file mode 100644 index 0000000000..fb041d2827 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionCCLicenseResource.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.SubmissionCCLicenseRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * CCLicense HAL Resource. This resource adds the data from the REST object together with embedded objects + * and a set of links if applicable + */ +@RelNameDSpaceResource(SubmissionCCLicenseRest.NAME) +public class SubmissionCCLicenseResource extends DSpaceResource { + public SubmissionCCLicenseResource(SubmissionCCLicenseRest submissionCCLicenseRest, Utils utils) { + super(submissionCCLicenseRest, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionCCLicenseUrlResource.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionCCLicenseUrlResource.java new file mode 100644 index 0000000000..29ce7cf669 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/hateoas/SubmissionCCLicenseUrlResource.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.SubmissionCCLicenseUrlRest; +import org.dspace.app.rest.model.hateoas.annotations.RelNameDSpaceResource; +import org.dspace.app.rest.utils.Utils; + +/** + * SubmissionCCLicenseUrl HAL Resource. This resource adds the data from the REST object together with embedded objects + * and a set of links if applicable + */ +@RelNameDSpaceResource(SubmissionCCLicenseUrlRest.NAME) +public class SubmissionCCLicenseUrlResource extends DSpaceResource { + public SubmissionCCLicenseUrlResource(SubmissionCCLicenseUrlRest submissionCCLicenseUrlRest, Utils utils) { + super(submissionCCLicenseUrlRest, utils); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataCCLicense.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataCCLicense.java new file mode 100644 index 0000000000..32b3710d7c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/step/DataCCLicense.java @@ -0,0 +1,46 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model.step; + +import org.dspace.app.rest.model.BitstreamRest; + +/** + * Java Bean to expose the section creativecommons representing the CC License during in progress submission. + */ +public class DataCCLicense implements SectionData { + + private String uri; + + private String rights; + + private BitstreamRest file; + + public String getUri() { + return uri; + } + + public void setUri(final String uri) { + this.uri = uri; + } + + public String getRights() { + return rights; + } + + public void setRights(final String rights) { + this.rights = rights; + } + + public BitstreamRest getFile() { + return file; + } + + public void setFile(final BitstreamRest file) { + this.file = file; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/wrapper/SubmissionCCLicenseUrl.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/wrapper/SubmissionCCLicenseUrl.java new file mode 100644 index 0000000000..68ff1166b4 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/wrapper/SubmissionCCLicenseUrl.java @@ -0,0 +1,68 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model.wrapper; + +/** + * This class represents a model implementation for {@link org.dspace.app.rest.model.SubmissionCCLicenseUrlRest} + * This will simply store a url and an id. it'll be used to create an object with these variables out of information + * that came from the back-end. This object will then be used in the + * {@link org.dspace.app.rest.converter.SubmissionCCLicenseUrlConverter} to turn it into its REST object + */ +public class SubmissionCCLicenseUrl { + + /** + * The url for ths object + */ + private String url; + /** + * The id for this object + */ + private String id; + + /** + * Default constructor with two parameters, url and id + * @param url The url of this object + * @param id The id of this object + */ + public SubmissionCCLicenseUrl(String url, String id) { + this.url = url; + this.id = id; + } + + /** + * Generic getter for the url + * @return the url value of this SubmissionCCLicenseUrl + */ + public String getUrl() { + return url; + } + + /** + * Generic setter for the url + * @param url The url to be set on this SubmissionCCLicenseUrl + */ + public void setUrl(String url) { + this.url = url; + } + + /** + * Generic getter for the id + * @return the id value of this SubmissionCCLicenseUrl + */ + public String getId() { + return id; + } + + /** + * Generic setter for the id + * @param id The id to be set on this SubmissionCCLicenseUrl + */ + public void setId(String id) { + this.id = id; + } +} 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 18120775d0..d5dda5a0bc 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 @@ -8,19 +8,24 @@ package org.dspace.app.rest.repository; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Set; +import org.dspace.app.rest.DiscoverableEndpointsService; import org.dspace.app.rest.model.AuthorityRest; +import org.dspace.app.rest.model.AuthorizationRest; 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; import org.dspace.core.Context; +import org.springframework.beans.factory.InitializingBean; 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.hateoas.Link; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -30,7 +35,8 @@ import org.springframework.stereotype.Component; * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ @Component(AuthorityRest.CATEGORY + "." + AuthorityRest.NAME) -public class AuthorityRestRepository extends DSpaceRestRepository { +public class AuthorityRestRepository extends DSpaceRestRepository + implements InitializingBean { @Autowired private ChoiceAuthorityService cas; @@ -38,6 +44,9 @@ public class AuthorityRestRepository extends DSpaceRestRepository getDomainClass() { return AuthorityRest.class; } + + @Override + public void afterPropertiesSet() throws Exception { + discoverableEndpointsService.register(this, Arrays.asList( + new Link("/api/" + AuthorizationRest.CATEGORY + "/" + AuthorizationRest.NAME + "/search", + AuthorizationRest.NAME + "-search"))); + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationFeatureRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationFeatureRestRepository.java index 418bebcdf5..62781fe8e8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationFeatureRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationFeatureRestRepository.java @@ -45,11 +45,10 @@ public class AuthorizationFeatureRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - return converter.toRestPage(utils.getPage(authorizationFeatureService.findAll(), - pageable), utils.obtainProjection()); + return converter.toRestPage(authorizationFeatureService.findAll(), pageable, utils.obtainProjection()); } - @PreAuthorize("hasAuthority('ADMIN')") + @PreAuthorize("permitAll()") @Override public AuthorizationFeatureRest findOne(Context context, String id) { AuthorizationFeature authzFeature = authorizationFeatureService.find(id); @@ -64,6 +63,6 @@ public class AuthorizationFeatureRestRepository extends DSpaceRestRepository findByResourceType(@Parameter(value = "type", required = true) String type, Pageable pageable) { List foundFeatures = authorizationFeatureService.findByResourceType(type); - return converter.toRestPage(utils.getPage(foundFeatures, pageable), utils.obtainProjection()); + return converter.toRestPage(foundFeatures, pageable, utils.obtainProjection()); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationRestRepository.java index fe15bbfbf7..175fcd49f9 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/AuthorizationRestRepository.java @@ -174,7 +174,7 @@ public class AuthorizationRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { try { List bit = bitstreamFormatService.findAll(context); - return converter.toRestPage(utils.getPage(bit, pageable), utils.obtainProjection()); + return converter.toRestPage(bit, pageable, utils.obtainProjection()); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } 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 2d29781c9b..71c02d4057 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 @@ -10,17 +10,14 @@ package org.dspace.app.rest.repository; import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; import java.util.UUID; import javax.servlet.http.HttpServletRequest; import org.dspace.app.rest.exception.DSpaceBadRequestException; +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; 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.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Bitstream; @@ -34,7 +31,6 @@ import org.dspace.content.service.CommunityService; 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; @@ -71,7 +67,7 @@ public class BitstreamRestRepository extends DSpaceObjectRestRepository findAll(Context context, Pageable pageable) { - List bit = new ArrayList(); - Iterator it = null; - int total = 0; - try { - total = bs.countTotal(context); - it = bs.findAll(context, pageable.getPageSize(), Math.toIntExact(pageable.getOffset())); - while (it.hasNext()) { - bit.add(it.next()); - } - } catch (SQLException e) { - throw new RuntimeException(e.getMessage(), e); - } - Projection projection = utils.obtainProjection(); - Page page = new PageImpl<>(bit, pageable, total) - .map((bitstream) -> converter.toRest(bitstream, projection)); - return page; + throw new RepositoryMethodNotImplementedException(BitstreamRest.NAME, "findAll"); } @Override 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 10f18e8e66..01277ff29b 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 @@ -16,6 +16,7 @@ import org.dspace.browse.BrowseIndex; import org.dspace.core.Context; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; /** @@ -27,6 +28,7 @@ import org.springframework.stereotype.Component; public class BrowseIndexRestRepository extends DSpaceRestRepository { @Override + @PreAuthorize("permitAll()") public BrowseIndexRest findOne(Context context, String name) { BrowseIndexRest bi = null; BrowseIndex bix; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleBitstreamLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleBitstreamLinkRepository.java index 4b280fdbce..b0a4488e03 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleBitstreamLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleBitstreamLinkRepository.java @@ -15,7 +15,6 @@ import javax.servlet.http.HttpServletRequest; 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.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.BundleService; @@ -52,8 +51,7 @@ public class BundleBitstreamLinkRepository extends AbstractDSpaceRestRepository throw new ResourceNotFoundException("No such bundle: " + bundleId); } Pageable pageable = utils.getPageable(optionalPageable); - Page page = utils.getPage(bundle.getBitstreams(), pageable); - return converter.toRestPage(page, projection); + return converter.toRestPage(bundle.getBitstreams(), pageable, projection); } catch (SQLException e) { throw new RuntimeException(e); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleRestRepository.java index d26ceeb2bf..f750743db6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleRestRepository.java @@ -74,11 +74,11 @@ public class BundleRestRepository extends DSpaceObjectRestRepository { +public class ClaimedTaskRestRepository extends DSpaceRestRepository + implements InitializingBean { private static final Logger log = Logger.getLogger(ClaimedTaskRestRepository.class); @@ -78,6 +83,9 @@ public class ClaimedTaskRestRepository extends DSpaceRestRepository tasks = claimedTaskService.findByEperson(context, ep); - return converter.toRestPage(utils.getPage(tasks, pageable), utils.obtainProjection()); + return converter.toRestPage(tasks, pageable, utils.obtainProjection()); } else { throw new RESTAuthorizationException("Only administrators can search for claimed tasks of other users"); } @@ -188,4 +196,11 @@ public class ClaimedTaskRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { throw new RepositoryMethodNotImplementedException(ClaimedTaskRest.NAME, "findAll"); } + + @Override + public void afterPropertiesSet() throws Exception { + discoverableEndpointsService.register(this, Arrays.asList( + new Link("/api/" + ClaimedTaskRest.CATEGORY + "/" + ClaimedTaskRest.NAME + "/search", + ClaimedTaskRest.NAME + "-search"))); + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionBitstreamReadGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionBitstreamReadGroupLinkRepository.java index 57b17168ea..c1b322a490 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionBitstreamReadGroupLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CollectionBitstreamReadGroupLinkRepository.java @@ -72,6 +72,9 @@ public class CollectionBitstreamReadGroupLinkRepository extends AbstractDSpaceRe } List bitstreamGroups = authorizeService .getAuthorizedGroups(context, collection, Constants.DEFAULT_BITSTREAM_READ); + if (bitstreamGroups == null || bitstreamGroups.isEmpty()) { + return null; + } Group bitstreamReadGroup = bitstreamGroups.get(0); if (bitstreamReadGroup == null) { 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 934a4fc698..f273d6434e 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 @@ -163,9 +163,10 @@ public class CollectionRestRepository extends DSpaceObjectRestRepository findAuthorizedByCommunity( - @Parameter(value = "uuid", required = true) UUID communityUuid, Pageable pageable) { + @SearchRestMethod(name = "findSubmitAuthorizedByCommunity") + public Page findSubmitAuthorizedByCommunity( + @Parameter(value = "uuid", required = true) UUID communityUuid, Pageable pageable, + @Parameter(value = "query") String q) { try { Context context = obtainContext(); Community com = communityService.find(context, communityUuid); @@ -174,19 +175,26 @@ public class CollectionRestRepository extends DSpaceObjectRestRepository collections = cs.findAuthorized(context, com, Constants.ADD); - return converter.toRestPage(utils.getPage(collections, pageable), utils.obtainProjection()); - } catch (SQLException e) { + List collections = cs.findCollectionsWithSubmit(q, context, com, + Math.toIntExact(pageable.getOffset()), + Math.toIntExact(pageable.getOffset() + pageable.getPageSize())); + int tot = cs.countCollectionsWithSubmit(q, context, com); + return converter.toRestPage(collections, pageable, tot , utils.obtainProjection()); + } catch (SQLException | SearchServiceException e) { throw new RuntimeException(e.getMessage(), e); } } - @SearchRestMethod(name = "findAuthorized") - public Page findAuthorized(Pageable pageable) { + @SearchRestMethod(name = "findSubmitAuthorized") + public Page findSubmitAuthorized(@Parameter(value = "query") String q, + Pageable pageable) throws SearchServiceException { try { Context context = obtainContext(); - List collections = cs.findAuthorizedOptimized(context, Constants.ADD); - return converter.toRestPage(utils.getPage(collections, pageable), utils.obtainProjection()); + List collections = cs.findCollectionsWithSubmit(q, context, null, + Math.toIntExact(pageable.getOffset()), + Math.toIntExact(pageable.getOffset() + pageable.getPageSize())); + int tot = cs.countCollectionsWithSubmit(q, context, null); + return converter.toRestPage(collections, pageable, tot, utils.obtainProjection()); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java index ca3183f265..885b680dce 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java @@ -201,7 +201,7 @@ public class CommunityRestRepository extends DSpaceObjectRestRepository findAllTop(Pageable pageable) { try { List communities = cs.findAllTop(obtainContext()); - return converter.toRestPage(utils.getPage(communities, pageable), utils.obtainProjection()); + return converter.toRestPage(communities, pageable, utils.obtainProjection()); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ConfigurationRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ConfigurationRestRepository.java new file mode 100644 index 0000000000..caadb9f6f3 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ConfigurationRestRepository.java @@ -0,0 +1,79 @@ +/** + * 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.Arrays; +import java.util.List; + +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.model.PropertyRest; +import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * This is the repository responsible of exposing configuration properties + */ +@Component(PropertyRest.CATEGORY + "." + PropertyRest.NAME) +public class ConfigurationRestRepository extends DSpaceRestRepository { + + private ConfigurationService configurationService; + private List exposedProperties; + + @Autowired + public ConfigurationRestRepository(ConfigurationService configurationService) { + this.configurationService = configurationService; + this.exposedProperties = Arrays.asList(configurationService.getArrayProperty("rest.properties.exposed")); + } + + /** + * Gets the value of a configuration property if it is exposed via REST + * + * Example: + *
+     * {@code
+     * curl http:///api/config/properties/google.analytics.key
+     *  -XGET \
+     *  -H 'Authorization: Bearer eyJhbGciOiJI...'
+     * }
+     * 
+ * + * @param property + * @return + */ + @Override + @PreAuthorize("permitAll()") + public PropertyRest findOne(Context context, String property) { + if (!exposedProperties.contains(property) || !configurationService.hasProperty(property)) { + throw new ResourceNotFoundException("No such configuration property: " + property); + } + + String[] propertyValues = configurationService.getArrayProperty(property); + + PropertyRest propertyRest = new PropertyRest(); + propertyRest.setName(property); + propertyRest.setValues(Arrays.asList(propertyValues)); + + return propertyRest; + } + + @Override + public Page findAll(Context context, Pageable pageable) { + throw new RepositoryMethodNotImplementedException("No implementation found; Method not allowed", ""); + } + + @Override + public Class getDomainClass() { + return PropertyRest.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java index 97c13a6656..e8bf235940 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java @@ -32,6 +32,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.multipart.MultipartFile; /** @@ -122,6 +123,7 @@ public abstract class DSpaceRestRepository groups = utils.getPage(eperson.getGroups(), optionalPageable); - return converter.toRestPage(groups, projection); + return converter.toRestPage(eperson.getGroups(), optionalPageable, projection); } catch (SQLException e) { throw new RuntimeException(e); } 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 073d1b25bd..e044346c2b 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 @@ -9,12 +9,14 @@ package org.dspace.app.rest.repository; import java.io.IOException; import java.sql.SQLException; +import java.util.Arrays; 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.dspace.app.rest.DiscoverableEndpointsService; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.exception.UnprocessableEntityException; @@ -25,9 +27,11 @@ import org.dspace.authorize.service.AuthorizeService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.service.EPersonService; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.hateoas.Link; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -39,11 +43,15 @@ import org.springframework.stereotype.Component; */ @Component(EPersonRest.CATEGORY + "." + EPersonRest.NAME) -public class EPersonRestRepository extends DSpaceObjectRestRepository { +public class EPersonRestRepository extends DSpaceObjectRestRepository + implements InitializingBean { @Autowired AuthorizeService authorizeService; + @Autowired + DiscoverableEndpointsService discoverableEndpointsService; + private final EPersonService es; @@ -147,7 +155,7 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository findByMetadata(@Parameter(value = "query", required = true) String query, Pageable pageable) { @@ -195,4 +203,10 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository getDomainClass() { return EPersonRest.class; } + + @Override + public void afterPropertiesSet() throws Exception { + discoverableEndpointsService.register(this, Arrays.asList( + new Link("/api/" + EPersonRest.CATEGORY + "/registrations", EPersonRest.NAME + "-registration"))); + } } 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 2e1b8f1321..29db39d879 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 @@ -18,6 +18,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; /** @@ -29,6 +30,8 @@ public class EntityTypeRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { try { List entityTypes = entityTypeService.findAll(context); - return converter.toRestPage(utils.getPage(entityTypes, pageable), utils.obtainProjection()); + return converter.toRestPage(entityTypes, pageable, utils.obtainProjection()); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } } + @Override public Class getDomainClass() { return EntityTypeRest.class; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java index 9b63e6c420..49a128cd85 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ExternalSourceRestRepository.java @@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; /** @@ -77,6 +78,7 @@ public class ExternalSourceRestRepository extends DSpaceRestRepository ePersons = utils.getPage(group.getMembers(), optionalPageable); - return converter.toRestPage(ePersons, projection); + return converter.toRestPage(group.getMembers(), optionalPageable, projection); } catch (SQLException e) { throw new RuntimeException(e); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupGroupLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupGroupLinkRepository.java index 952fc62bf5..37cf9083b3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupGroupLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/GroupGroupLinkRepository.java @@ -45,8 +45,7 @@ public class GroupGroupLinkRepository extends AbstractDSpaceRestRepository if (group == null) { throw new ResourceNotFoundException("No such group: " + groupId); } - Page groups = utils.getPage(group.getMemberGroups(), optionalPageable); - return converter.toRestPage(groups, projection); + return converter.toRestPage(group.getMemberGroups(), optionalPageable, projection); } catch (SQLException e) { throw new RuntimeException(e); } 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 8310533597..b531c4fcb7 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 @@ -131,7 +131,7 @@ public class GroupRestRepository extends DSpaceObjectRestRepository findByMetadata(@Parameter(value = "query", required = true) String query, Pageable pageable) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemBundleLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemBundleLinkRepository.java index 43f452174d..d7525c881a 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemBundleLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemBundleLinkRepository.java @@ -15,7 +15,6 @@ import javax.servlet.http.HttpServletRequest; import org.dspace.app.rest.model.BundleRest; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.projection.Projection; -import org.dspace.content.Bundle; import org.dspace.content.Item; import org.dspace.content.service.ItemService; import org.dspace.core.Context; @@ -47,8 +46,7 @@ public class ItemBundleLinkRepository extends AbstractDSpaceRestRepository if (item == null) { throw new ResourceNotFoundException("No such item: " + itemId); } - Page bundlePage = utils.getPage(item.getBundles(), optionalPageable); - return converter.toRestPage(bundlePage, projection); + return converter.toRestPage(item.getBundles(), optionalPageable, projection); } catch (SQLException e) { throw new RuntimeException(e); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemMappedCollectionLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemMappedCollectionLinkRepository.java index 8fe1354a05..c632cd9d61 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemMappedCollectionLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ItemMappedCollectionLinkRepository.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.repository; import java.sql.SQLException; +import java.util.List; import java.util.UUID; import java.util.stream.Collectors; import javax.annotation.Nullable; @@ -49,10 +50,10 @@ public class ItemMappedCollectionLinkRepository extends AbstractDSpaceRestReposi throw new ResourceNotFoundException("No such item: " + itemId); } UUID owningCollectionId = item.getOwningCollection() == null ? null : item.getOwningCollection().getID(); - Page mappedCollectionPage = utils.getPage(item.getCollections().stream() - .filter((collection) -> !collection.getID().equals(owningCollectionId)) - .collect(Collectors.toList()), optionalPageable); - return converter.toRestPage(mappedCollectionPage, projection); + List collections = item.getCollections().stream() + .filter((collection) -> !collection.getID().equals(owningCollectionId)) + .collect(Collectors.toList()); + return converter.toRestPage(collections, optionalPageable, projection); } catch (SQLException e) { throw new RuntimeException(e); } 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 1ca8ecc1dd..b7764b81dc 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 @@ -53,6 +53,7 @@ public class MetadataFieldRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { try { List metadataFields = metadataFieldService.findAll(context); - return converter.toRestPage(utils.getPage(metadataFields, pageable), utils.obtainProjection()); + return converter.toRestPage(metadataFields, pageable, utils.obtainProjection()); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } @@ -86,7 +87,7 @@ public class MetadataFieldRestRepository extends DSpaceRestRepository metadataFields = metadataFieldService.findAllInSchema(context, schema); - return converter.toRestPage(utils.getPage(metadataFields, pageable), utils.obtainProjection()); + return converter.toRestPage(metadataFields, pageable, utils.obtainProjection()); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataSchemaRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataSchemaRestRepository.java index 28221c3cba..8f29970b18 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataSchemaRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataSchemaRestRepository.java @@ -45,6 +45,7 @@ public class MetadataSchemaRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { try { List metadataSchemas = metadataSchemaService.findAll(context); - return converter.toRestPage(utils.getPage(metadataSchemas, pageable), utils.obtainProjection()); + return converter.toRestPage(metadataSchemas, pageable, utils.obtainProjection()); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/PoolTaskRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/PoolTaskRestRepository.java index cddc1e2286..9faa86a09c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/PoolTaskRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/PoolTaskRestRepository.java @@ -9,12 +9,14 @@ package org.dspace.app.rest.repository; import java.io.IOException; import java.sql.SQLException; +import java.util.Arrays; import java.util.List; import java.util.UUID; import javax.mail.MessagingException; import javax.servlet.http.HttpServletRequest; import org.apache.log4j.Logger; +import org.dspace.app.rest.DiscoverableEndpointsService; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.exception.RESTAuthorizationException; @@ -36,10 +38,12 @@ import org.dspace.xmlworkflow.state.Workflow; import org.dspace.xmlworkflow.state.actions.WorkflowActionConfig; import org.dspace.xmlworkflow.storedcomponents.PoolTask; import org.dspace.xmlworkflow.storedcomponents.service.PoolTaskService; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.hateoas.Link; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -50,7 +54,8 @@ import org.springframework.stereotype.Component; */ @Component(PoolTaskRest.CATEGORY + "." + PoolTaskRest.NAME) -public class PoolTaskRestRepository extends DSpaceRestRepository { +public class PoolTaskRestRepository extends DSpaceRestRepository + implements InitializingBean { private static final Logger log = Logger.getLogger(PoolTaskRestRepository.class); @@ -72,6 +77,9 @@ public class PoolTaskRestRepository extends DSpaceRestRepository tasks = poolTaskService.findByEperson(context, ep); - return converter.toRestPage(utils.getPage(tasks, pageable), utils.obtainProjection()); + return converter.toRestPage(tasks, pageable, utils.obtainProjection()); } else { throw new RESTAuthorizationException("Only administrators can search for pool tasks of other users"); } @@ -146,4 +154,11 @@ public class PoolTaskRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { throw new RuntimeException("Method not allowed!"); } + + @Override + public void afterPropertiesSet() throws Exception { + discoverableEndpointsService.register(this, Arrays.asList( + new Link("/api/" + PoolTaskRest.CATEGORY + "/" + PoolTaskRest.NAME + "/search", + PoolTaskRest.NAME + "-search"))); + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessFileTypesLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessFileTypesLinkRepository.java new file mode 100644 index 0000000000..8eb8d7ef65 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessFileTypesLinkRepository.java @@ -0,0 +1,67 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import java.sql.SQLException; +import java.util.List; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.ProcessFileTypesRest; +import org.dspace.app.rest.model.ProcessRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.authorize.AuthorizeException; +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.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * This LinkRepository will deal with calls to the /filetypes endpoint of a given Process. + * It'll retrieve all the bitstreams for the given Process and return a {@link ProcessFileTypesRest} object that holds + * a list of Strings where each String represents a unique fileType of the Bitstreams for that Process + */ +@Component(ProcessRest.CATEGORY + "." + ProcessRest.NAME + "." + ProcessRest.FILE_TYPES) +public class ProcessFileTypesLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { + + @Autowired + private ProcessService processService; + + /** + * This will be the admin only endpoint that returns the {@link ProcessFileTypesRest} constructed with the values + * found in the Bitstreams of the Process with the given ProcessId + * @param request The relevant request + * @param processId The processId of the Process to be used + * @param optionalPageable Paging if applicable + * @param projection The current projection + * @return The {@link ProcessFileTypesRest} created from the Bitstreams of the given Process + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong + */ + @PreAuthorize("hasAuthority('ADMIN')") + public ProcessFileTypesRest getFileTypesFromProcess(@Nullable HttpServletRequest request, + Integer processId, + @Nullable Pageable optionalPageable, + Projection projection) throws SQLException, AuthorizeException { + + Context context = obtainContext(); + Process process = processService.find(context, processId); + if (process == null) { + throw new ResourceNotFoundException("Process with id " + processId + " was not found"); + } + List fileTypes = processService.getFileTypesForProcessBitstreams(context, process); + ProcessFileTypesRest processFileTypesRest = new ProcessFileTypesRest(); + processFileTypesRest.setId("filetypes-" + processId); + processFileTypesRest.setValues(fileTypes); + return processFileTypesRest; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessFilesLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessFilesLinkRepository.java new file mode 100644 index 0000000000..42fcef0d62 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ProcessFilesLinkRepository.java @@ -0,0 +1,82 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import java.sql.SQLException; +import java.util.List; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.model.BitstreamRest; +import org.dspace.app.rest.model.ProcessRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.authorize.AuthorizeException; +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; + +/** + * This is the {@link LinkRestRepository} implementation that takes care of retrieving the list of + * {@link org.dspace.content.Bitstream} objects for the Process endpoints + * + */ +@Component(ProcessRest.CATEGORY + "." + ProcessRest.NAME + "." + ProcessRest.FILES) +public class ProcessFilesLinkRepository extends AbstractDSpaceRestRepository implements LinkRestRepository { + + private static final Logger log = LogManager.getLogger(); + + @Autowired + private ProcessRestRepository processRestRepository; + + /** + * This method will retrieve all the files from the process + * @param request The current request + * @param processId The processId for the Process to use + * @param optionalPageable Pageable if applicable + * @param projection Projection if applicable + * @return A list of {@link BitstreamRest} objects filled + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong + */ + @PreAuthorize("hasAuthority('ADMIN')") + public Page getFilesFromProcess(@Nullable HttpServletRequest request, + Integer processId, + @Nullable Pageable optionalPageable, + Projection projection) throws SQLException, AuthorizeException { + + List list = processRestRepository.getProcessBitstreams(processId); + Pageable pageable = utils.getPageable(optionalPageable); + return utils.getPage(list, pageable); + } + + /** + * This method will retrieve a bitstream for the given processId for the given fileType + * @param request The current request + * @param processId The processId for the process to search in + * @param fileType The filetype that the bitstream has to be + * @param pageable Pageable if applicable + * @param projection The current projection + * @return The BitstreamRest object that corresponds with the Process and type + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong + */ + @PreAuthorize("hasPermission(#processId, 'PROCESS', 'READ')") + public BitstreamRest getResource(HttpServletRequest request, String processId, String fileType, + Pageable pageable, Projection projection) + throws SQLException, AuthorizeException { + if (log.isTraceEnabled()) { + log.trace("Retrieving Files with type " + fileType + " from Process with ID: " + processId); + } + + return processRestRepository.getProcessBitstreamByType(Integer.parseInt(processId), fileType); + } +} 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 index 7267a23cf3..1a545ce5e0 100644 --- 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 @@ -7,17 +7,27 @@ */ package org.dspace.app.rest.repository; +import java.io.IOException; import java.sql.SQLException; import java.util.List; +import java.util.stream.Collectors; import org.apache.log4j.Logger; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.model.ProcessRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Bitstream; 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.data.rest.webmvc.ResourceNotFoundException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -32,6 +42,14 @@ public class ProcessRestRepository extends DSpaceRestRepository getProcessBitstreams(Integer processId) throws SQLException, AuthorizeException { + Context context = obtainContext(); + Process process = getProcess(processId, context); + List bitstreams = processService.getBitstreams(context, process); + return bitstreams.stream() + .map(bitstream -> (BitstreamRest) converterService.toRest(bitstream, Projection.DEFAULT)) + .collect(Collectors.toList()); + } + + private Process getProcess(Integer processId, Context context) throws SQLException, AuthorizeException { + Process process = processService.find(context, processId); + if (process == null) { + throw new ResourceNotFoundException("Process with id " + processId + " was not found"); + } + if ((context.getCurrentUser() == null) || (!context.getCurrentUser() + .equals(process.getEPerson()) && !authorizeService + .isAdmin(context))) { + throw new AuthorizeException("The current user is not eligible to view the process with id: " + processId); + } + return process; + } + + /** + * Retrieves the Bitstream in the given Process of a given type + * @param processId The processId of the Process to be used + * @param type The type of bitstreams to be returned, if null it'll return all the bitstreams + * @return The bitstream for the given parameters + * @throws SQLException If something goes wrong + * @throws AuthorizeException If something goes wrong + */ + public BitstreamRest getProcessBitstreamByType(Integer processId, String type) + throws SQLException, AuthorizeException { + Context context = obtainContext(); + Process process = getProcess(processId, context); + Bitstream bitstream = processService.getBitstream(context, process, type); + + return converterService.toRest(bitstream, utils.obtainProjection()); + } + + @Override + protected void delete(Context context, Integer integer) + throws AuthorizeException, RepositoryMethodNotImplementedException { + try { + processService.delete(context, processService.find(context, integer)); + } catch (SQLException | IOException e) { + log.error("Something went wrong trying to find Process with id: " + integer, 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 40a66f1208..5eee50d61f 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 @@ -40,6 +40,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; /** @@ -71,6 +72,7 @@ public class RelationshipRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { try { List relationshipTypes = relationshipTypeService.findAll(context); - return converter.toRestPage(utils.getPage(relationshipTypes, pageable), utils.obtainProjection()); + return converter.toRestPage(relationshipTypes, pageable, utils.obtainProjection()); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } 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 009e0c4594..e67bcfa040 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,13 @@ package org.dspace.app.rest.repository; import java.io.IOException; import java.sql.SQLException; +import java.util.Arrays; import java.util.List; import java.util.UUID; import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.ObjectMapper; +import org.dspace.app.rest.DiscoverableEndpointsService; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.exception.MissingParameterException; @@ -35,10 +37,12 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.GroupService; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.hateoas.Link; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; @@ -48,7 +52,8 @@ import org.springframework.stereotype.Component; * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ @Component(ResourcePolicyRest.CATEGORY + "." + ResourcePolicyRest.NAME) -public class ResourcePolicyRestRepository extends DSpaceRestRepository { +public class ResourcePolicyRestRepository extends DSpaceRestRepository + implements InitializingBean { @Autowired ResourcePolicyService resourcePolicyService; @@ -68,6 +73,9 @@ public class ResourcePolicyRestRepository extends DSpaceRestRepository resourcePatch; + @Autowired + DiscoverableEndpointsService discoverableEndpointsService; + @Override @PreAuthorize("hasPermission(#id, 'resourcepolicy', 'READ')") public ResourcePolicyRest findOne(Context context, Integer id) { @@ -312,4 +320,11 @@ public class ResourcePolicyRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { - List dSpaceRunnables = scriptService.getDSpaceRunnables(context); - return converter.toRestPage(utils.getPage(dSpaceRunnables, pageable), utils.obtainProjection()); + List scriptConfigurations = scriptService.getScriptConfigurations(context); + return converter.toRestPage(scriptConfigurations, pageable, utils.obtainProjection()); } @Override @@ -84,12 +89,12 @@ public class ScriptRestRepository extends DSpaceRestRepository files) throws SQLException, + IOException, AuthorizeException, IllegalAccessException, InstantiationException { String properties = requestService.getCurrentRequest().getServletRequest().getParameter("properties"); List dSpaceCommandLineParameters = processPropertiesToDSpaceCommandLineParameters(properties); - DSpaceRunnable scriptToExecute = scriptService.getScriptForName(scriptName); + ScriptConfiguration scriptToExecute = scriptService.getScriptConfiguration(scriptName); if (scriptToExecute == null) { throw new DSpaceBadRequestException("The script for name: " + scriptName + " wasn't found"); } @@ -99,16 +104,8 @@ public class ScriptRestRepository extends DSpaceRestRepository args = constructArgs(dSpaceCommandLineParameters); - try { - runDSpaceScript(scriptToExecute, restDSpaceRunnableHandler, args); - context.complete(); - return converter.toRest(restDSpaceRunnableHandler.getProcess(), utils.obtainProjection()); - } catch (SQLException e) { - log.error("Failed to create a process with user: " + context.getCurrentUser() + - " scriptname: " + scriptName + " and parameters " + DSpaceCommandLineParameter - .concatenate(dSpaceCommandLineParameters), e); - } - return null; + runDSpaceScript(files, context, scriptToExecute, restDSpaceRunnableHandler, args); + return converter.toRest(restDSpaceRunnableHandler.getProcess(context), utils.obtainProjection()); } private List processPropertiesToDSpaceCommandLineParameters(String propertiesJson) @@ -137,13 +134,17 @@ public class ScriptRestRepository extends DSpaceRestRepository args) { + private void runDSpaceScript(List files, Context context, ScriptConfiguration scriptToExecute, + RestDSpaceRunnableHandler restDSpaceRunnableHandler, List args) + throws IOException, SQLException, AuthorizeException, InstantiationException, IllegalAccessException { + DSpaceRunnable dSpaceRunnable = scriptService.createDSpaceRunnableForScriptConfiguration(scriptToExecute); try { - scriptToExecute.initialize(args.toArray(new String[0]), restDSpaceRunnableHandler); - restDSpaceRunnableHandler.schedule(scriptToExecute); + dSpaceRunnable.initialize(args.toArray(new String[0]), restDSpaceRunnableHandler, context.getCurrentUser()); + checkFileNames(dSpaceRunnable, files); + processFiles(context, restDSpaceRunnableHandler, files); + restDSpaceRunnableHandler.schedule(dSpaceRunnable); } catch (ParseException e) { - scriptToExecute.printHelp(); + dSpaceRunnable.printHelp(); restDSpaceRunnableHandler .handleException( "Failed to parse the arguments given to the script with name: " + scriptToExecute.getName() @@ -151,4 +152,37 @@ public class ScriptRestRepository extends DSpaceRestRepository files) + throws IOException, SQLException, AuthorizeException { + for (MultipartFile file : files) { + restDSpaceRunnableHandler + .writeFilestream(context, file.getOriginalFilename(), file.getInputStream(), "inputfile"); + } + } + + /** + * This method checks if the files referenced in the options are actually present for the request + * If this isn't the case, we'll abort the script now instead of creating issues later on + * @param dSpaceRunnable The script that we'll attempt to run + * @param files The list of files in the request + */ + private void checkFileNames(DSpaceRunnable dSpaceRunnable, List files) { + List fileNames = new LinkedList<>(); + for (MultipartFile file : files) { + String fileName = file.getOriginalFilename(); + if (fileNames.contains(fileName)) { + throw new UnprocessableEntityException("There are two files with the same name: " + fileName); + } else { + fileNames.add(fileName); + } + } + + List fileNamesFromOptions = dSpaceRunnable.getFileNamesFromInputStreamOptions(); + if (!fileNames.containsAll(fileNamesFromOptions)) { + throw new UnprocessableEntityException("Files given in properties aren't all present in the request"); + } + } + + } 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 2d29c6fffe..bf13700078 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 @@ -43,6 +43,7 @@ public class SiteRestRepository extends DSpaceObjectRestRepository findAll(Context context, Pageable pageable) { try { List sites = Arrays.asList(sitesv.findSite(context)); - return converter.toRestPage(sites, pageable, 1L, utils.obtainProjection()); + return converter.toRestPage(sites, pageable, utils.obtainProjection()); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionCCLicenseRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionCCLicenseRestRepository.java new file mode 100644 index 0000000000..0dab42f9bd --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionCCLicenseRestRepository.java @@ -0,0 +1,54 @@ +/** + * 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.List; + +import org.dspace.app.rest.model.SubmissionCCLicenseRest; +import org.dspace.core.Context; +import org.dspace.license.CCLicense; +import org.dspace.license.service.CreativeCommonsService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * This is the repository that is responsible to manage CCLicense Rest objects + */ +@Component(SubmissionCCLicenseRest.CATEGORY + "." + SubmissionCCLicenseRest.NAME) +public class SubmissionCCLicenseRestRepository extends DSpaceRestRepository { + + @Autowired + protected CreativeCommonsService creativeCommonsService; + + @Override + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public SubmissionCCLicenseRest findOne(final Context context, final String licenseId) { + CCLicense ccLicense = creativeCommonsService.findOne(licenseId); + if (ccLicense == null) { + throw new ResourceNotFoundException("No CC license could be found for ID: " + licenseId ); + } + return converter.toRest(ccLicense, utils.obtainProjection()); + } + + @Override + @PreAuthorize("hasAuthority('AUTHENTICATED')") + public Page findAll(final Context context, final Pageable pageable) { + + List allCCLicenses = creativeCommonsService.findAllCCLicenses(); + return converter.toRestPage(allCCLicenses, pageable, utils.obtainProjection()); + } + + @Override + public Class getDomainClass() { + return SubmissionCCLicenseRest.class; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/TemplateItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/TemplateItemRestRepository.java index 2e1436d4fb..ad80140481 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/TemplateItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/TemplateItemRestRepository.java @@ -28,6 +28,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; /** @@ -49,6 +50,7 @@ public class TemplateItemRestRepository extends DSpaceRestRepository resourcePatch; @Override + @PreAuthorize("permitAll()") public TemplateItemRest findOne(Context context, UUID uuid) { Item item = null; try { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionsLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionsLinkRepository.java index 4b17b46c91..e188b04950 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionsLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/VersionsLinkRepository.java @@ -64,6 +64,6 @@ public class VersionsLinkRepository extends AbstractDSpaceRestRepository } List versions = versioningService.getVersionsByHistory(context, versionHistory); Pageable pageable = optionalPageable != null ? optionalPageable : PageRequest.of(0, 20); - return converter.toRestPage(utils.getPage(versions, pageable), projection); + return converter.toRestPage(versions, pageable, projection); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowDefinitionRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowDefinitionRestRepository.java index 6837168521..7aa6d7d280 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowDefinitionRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkflowDefinitionRestRepository.java @@ -61,7 +61,7 @@ public class WorkflowDefinitionRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { List workflows = xmlWorkflowFactory.getAllConfiguredWorkflows(); - return converter.toRestPage(utils.getPage(workflows, pageable), utils.obtainProjection()); + return converter.toRestPage(workflows, pageable, utils.obtainProjection()); } /** 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 index 6fd1aa10fc..f2080dcd84 100644 --- 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 @@ -7,16 +7,23 @@ */ package org.dspace.app.rest.scripts.handler.impl; +import java.io.IOException; +import java.io.InputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.sql.SQLException; import java.util.List; +import java.util.Optional; 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.authorize.AuthorizeException; +import org.dspace.content.Bitstream; import org.dspace.content.ProcessStatus; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.BitstreamService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.scripts.DSpaceCommandLineParameter; @@ -25,6 +32,8 @@ import org.dspace.scripts.Process; import org.dspace.scripts.factory.ScriptServiceFactory; import org.dspace.scripts.handler.DSpaceRunnableHandler; import org.dspace.scripts.service.ProcessService; +import org.dspace.utils.DSpace; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; /** * The {@link DSpaceRunnableHandler} dealing with Scripts started from the REST api @@ -33,6 +42,7 @@ public class RestDSpaceRunnableHandler implements DSpaceRunnableHandler { private static final Logger log = org.apache.logging.log4j.LogManager .getLogger(RestDSpaceRunnableHandler.class); + private BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); private ProcessService processService = ScriptServiceFactory.getInstance().getProcessService(); private Integer processId; @@ -176,20 +186,41 @@ public class RestDSpaceRunnableHandler implements DSpaceRunnableHandler { } } + @Override + public Optional getFileStream(Context context, String fileName) throws IOException, + AuthorizeException { + try { + Process process = processService.find(context, processId); + Bitstream bitstream = processService.getBitstreamByName(context, process, fileName); + InputStream inputStream = bitstreamService.retrieve(context, bitstream); + if (inputStream == null) { + return Optional.empty(); + } else { + return Optional.of(inputStream); + } + } catch (SQLException sqlException) { + log.error("SQL exception while attempting to find process", sqlException); + } + return null; + } + + @Override + public void writeFilestream(Context context, String fileName, InputStream inputStream, String type) + throws IOException, SQLException, AuthorizeException { + Process process = processService.find(context, processId); + processService.appendFile(context, process, inputStream, type, fileName); + } + /** * This method will return the process created by this handler * @return The Process database object created by this handler + * @param context */ - public Process getProcess() { - Context context = new Context(); + public Process getProcess(Context 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; } @@ -200,6 +231,9 @@ public class RestDSpaceRunnableHandler implements DSpaceRunnableHandler { * @param script The script to be ran */ public void schedule(DSpaceRunnable script) { + ThreadPoolTaskExecutor taskExecutor = new DSpace().getServiceManager() + .getServiceByName("dspaceRunnableThreadExecutor", + ThreadPoolTaskExecutor.class); Context context = new Context(); try { Process process = processService.find(context, processId); @@ -213,6 +247,6 @@ public class RestDSpaceRunnableHandler implements DSpaceRunnableHandler { context.abort(); } } - script.run(); + taskExecutor.execute(script); } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/BitstreamMetadataReadPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/BitstreamMetadataReadPermissionEvaluatorPlugin.java new file mode 100644 index 0000000000..b23589cf3a --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/BitstreamMetadataReadPermissionEvaluatorPlugin.java @@ -0,0 +1,99 @@ +/** + * 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 java.util.UUID; + +import org.dspace.app.rest.repository.BitstreamRestRepository; +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.rest.utils.DSpaceObjectUtils; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.content.service.BitstreamService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +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; + +/** + * Used by {@link BitstreamRestRepository#findOne(Context, UUID)} to get metadata of private bitstreams even though user + * can't access actual file + * + * @author Maria Verdonck (Atmire) on 15/06/2020 + */ +@Component +public class BitstreamMetadataReadPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { + + private static final Logger log = LoggerFactory.getLogger(BitstreamMetadataReadPermissionEvaluatorPlugin.class); + + @Autowired + private RequestService requestService; + @Autowired + private DSpaceObjectUtils dspaceObjectUtil; + @Autowired + AuthorizeService authorizeService; + @Autowired + protected BitstreamService bitstreamService; + + private final static String METADATA_READ_PERMISSION = "METADATA_READ"; + + @Override + public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, + Object permission) { + if (permission.toString().equalsIgnoreCase(METADATA_READ_PERMISSION) && targetId != null) { + Request request = requestService.getCurrentRequest(); + Context context = ContextUtil.obtainContext(request.getServletRequest()); + + try { + UUID dsoUuid = UUID.fromString(targetId.toString()); + DSpaceObject dso = dspaceObjectUtil.findDSpaceObject(context, dsoUuid); + if (dso instanceof Bitstream) { + if (authorizeService.isAdmin(context, dso)) { + // Is Admin on bitstream + return true; + } + if (authorizeService.authorizeActionBoolean(context, dso, Constants.READ)) { + // Has READ rights on bitstream + return true; + } + DSpaceObject bitstreamParentObject = bitstreamService.getParentObject(context, (Bitstream) dso); + if (bitstreamParentObject instanceof Item && !((Bitstream) dso).getBundles().isEmpty()) { + // If parent is item and it is in a bundle + Bundle firstBundle = ((Bitstream) dso).getBundles().get(0); + if (authorizeService.authorizeActionBoolean(context, bitstreamParentObject, Constants.READ) + && authorizeService.authorizeActionBoolean(context, firstBundle, Constants.READ)) { + // Has READ rights on bitstream's parent item AND first bundle bitstream is in + return true; + } + } + } + } catch (SQLException e) { + log.error(e.getMessage(), e); + } + } + return false; + } + + @Override + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, + DSpaceRestPermission restPermission) { + // No need to override, since only user by super.hasPermission(Authentication authentication, Serializable + // targetId, String targetType, Object permission) which is overrode above + return false; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java index 7cfd451045..a470515419 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestAuthenticationProvider.java @@ -18,6 +18,7 @@ import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.util.AuthorizeUtil; import org.dspace.authenticate.AuthenticationMethod; import org.dspace.authenticate.service.AuthenticationService; import org.dspace.authorize.service.AuthorizeService; @@ -47,6 +48,8 @@ public class EPersonRestAuthenticationProvider implements AuthenticationProvider private static final Logger log = LoggerFactory.getLogger(EPersonRestAuthenticationProvider.class); + public static final String MANAGE_ACCESS_GROUP = "MANAGE_ACCESS_GROUP"; + @Autowired private AuthenticationService authenticationService; @@ -140,14 +143,21 @@ public class EPersonRestAuthenticationProvider implements AuthenticationProvider if (eperson != null) { boolean isAdmin = false; + boolean isCommunityAdmin = false; + boolean isCollectionAdmin = false; try { isAdmin = authorizeService.isAdmin(context, eperson); + isCommunityAdmin = authorizeService.isCommunityAdmin(context, eperson); + isCollectionAdmin = authorizeService.isCollectionAdmin(context, eperson); } catch (SQLException e) { log.error("SQL error while checking for admin rights", e); } if (isAdmin) { authorities.add(new SimpleGrantedAuthority(ADMIN_GRANT)); + } else if ((isCommunityAdmin && AuthorizeUtil.canCommunityAdminManageAccounts()) + || (isCollectionAdmin && AuthorizeUtil.canCollectionAdminManageAccounts())) { + authorities.add(new SimpleGrantedAuthority(MANAGE_ACCESS_GROUP)); } authorities.add(new SimpleGrantedAuthority(AUTHENTICATED_GRANT)); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java index ca13277b04..00c2c60cb2 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/EPersonRestPermissionEvaluatorPlugin.java @@ -17,6 +17,7 @@ import org.dspace.app.rest.model.patch.Patch; import org.dspace.app.rest.repository.patch.operation.DSpaceObjectMetadataPatchUtils; import org.dspace.app.rest.repository.patch.operation.EPersonPasswordReplaceOperation; import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.util.AuthorizeUtil; import org.dspace.authorize.service.AuthorizeService; import org.dspace.core.Constants; import org.dspace.core.Context; @@ -74,9 +75,13 @@ public class EPersonRestPermissionEvaluatorPlugin extends RestObjectPermissionEv // anonymous user if (ePerson == null) { return false; - } - - if (dsoId.equals(ePerson.getID())) { + } else if (dsoId.equals(ePerson.getID())) { + return true; + } else if (authorizeService.isCommunityAdmin(context, ePerson) + && AuthorizeUtil.canCommunityAdminManageAccounts()) { + return true; + } else if (authorizeService.isCollectionAdmin(context, ePerson) + && AuthorizeUtil.canCollectionAdminManageAccounts()) { return true; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GroupRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GroupRestPermissionEvaluatorPlugin.java index 91cc302aa1..7f793c3b8d 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GroupRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/GroupRestPermissionEvaluatorPlugin.java @@ -12,6 +12,8 @@ import java.sql.SQLException; import java.util.UUID; import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.app.util.AuthorizeUtil; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.eperson.EPerson; @@ -44,6 +46,9 @@ public class GroupRestPermissionEvaluatorPlugin extends RestObjectPermissionEval @Autowired private EPersonService ePersonService; + @Autowired + AuthorizeService authorizeService; + @Override public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, DSpaceRestPermission permission) { @@ -64,7 +69,16 @@ public class GroupRestPermissionEvaluatorPlugin extends RestObjectPermissionEval Group group = groupService.find(context, dsoId); - if (groupService.isMember(context, ePerson, group)) { + // anonymous user + if (ePerson == null) { + return false; + } else if (groupService.isMember(context, ePerson, group)) { + return true; + } else if (authorizeService.isCommunityAdmin(context, ePerson) + && AuthorizeUtil.canCommunityAdminManageAccounts()) { + return true; + } else if (authorizeService.isCollectionAdmin(context, ePerson) + && AuthorizeUtil.canCollectionAdminManageAccounts()) { return true; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestObjectPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestObjectPermissionEvaluatorPlugin.java index 6ee17f8420..f35b0d26ef 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestObjectPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/RestObjectPermissionEvaluatorPlugin.java @@ -9,6 +9,7 @@ package org.dspace.app.rest.security; import java.io.Serializable; +import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.app.rest.model.patch.Patch; import org.springframework.security.core.Authentication; @@ -34,7 +35,8 @@ public abstract class RestObjectPermissionEvaluatorPlugin implements RestPermis public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { BaseObjectRest restObject = (BaseObjectRest) targetDomainObject; - return hasPermission(authentication, restObject.getId(), restObject.getType(), permission); + return hasPermission(authentication, restObject.getId(), StringUtils.upperCase(restObject.getType()), + permission); } @Override diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubmissionCCLicenseRestEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubmissionCCLicenseRestEvaluatorPlugin.java new file mode 100644 index 0000000000..ae925fe8b1 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubmissionCCLicenseRestEvaluatorPlugin.java @@ -0,0 +1,28 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.security; + +import java.io.Serializable; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.model.SubmissionCCLicenseRest; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +@Component +public class SubmissionCCLicenseRestEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { + + @Override + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, + DSpaceRestPermission restPermission) { + if (!StringUtils.equalsIgnoreCase(SubmissionCCLicenseRest.NAME, targetType)) { + return false; + } + return true; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubmissionCCLicenseUrlRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubmissionCCLicenseUrlRestPermissionEvaluatorPlugin.java new file mode 100644 index 0000000000..2fd8c7647f --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/SubmissionCCLicenseUrlRestPermissionEvaluatorPlugin.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.security; + +import java.io.Serializable; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.model.SubmissionCCLicenseUrlRest; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +/** + * This class will handle calls made to SubmissionCCLicenseUrlRest endpoints. + * It will return true because access can be granted anytime it's linked from another resource + */ +@Component +public class SubmissionCCLicenseUrlRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { + @Override + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, + DSpaceRestPermission restPermission) { + if (!StringUtils.equalsIgnoreCase(SubmissionCCLicenseUrlRest.NAME, targetType)) { + return false; + } + return true; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityExpressionEvaluator.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityExpressionEvaluator.java new file mode 100644 index 0000000000..364e93e297 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WebSecurityExpressionEvaluator.java @@ -0,0 +1,95 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.security; + +import java.util.List; +import javax.servlet.FilterChain; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.core.GenericTypeResolver; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.security.access.expression.ExpressionUtils; +import org.springframework.security.access.expression.SecurityExpressionHandler; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.FilterInvocation; +import org.springframework.stereotype.Component; + +/** + * This class will contain the logic to allow us to evaluate an expression given through a String. + * This will be used by the {@link org.dspace.app.rest.converter.ConverterService} for parsing + * the {@link org.springframework.security.access.prepost.PreAuthorize} annotations used on the findOne + * methods of RestRepositories. A String will be given to the evaluate method and that String will then + * be parsed and a boolean will be returned based on the condition in the String. + * For example: "hasPermission(#id, 'ITEM', 'READ')" is such a String + * This will be evaluated and if the current user has the permission to read an item with the given id, + * a true will be returned, if not it'll be false. + * This works on all the methods in {@link org.springframework.security.access.expression.SecurityExpressionRoot} + */ +@Component +public class WebSecurityExpressionEvaluator { + + private static final FilterChain EMPTY_CHAIN = (request, response) -> { + throw new UnsupportedOperationException(); + }; + + private final List securityExpressionHandlers; + + /** + * Constructor for this class that sets all the {@link SecurityExpressionHandler} objects in a list + * @param securityExpressionHandlers The {@link SecurityExpressionHandler} for this class + */ + public WebSecurityExpressionEvaluator(List securityExpressionHandlers) { + this.securityExpressionHandlers = securityExpressionHandlers; + } + + /** + * This method will have to be used to evaluate the String given. It'll parse the String and resolve + * it to a method in {@link org.springframework.security.access.expression.SecurityExpressionRoot} + * and evaluate it to then return a boolean + * @param securityExpression The String that resembles the expression that has to be parsed + * @param request The current request + * @param response The current response + * @param id The id for the Object that is the subject of the permission + * @return A boolean indicating whether the currentUser adheres to the + * permissions in the securityExpression String or not + */ + public boolean evaluate(String securityExpression, HttpServletRequest request, HttpServletResponse response, + String id) { + SecurityExpressionHandler handler = getFilterSecurityHandler(); + + Expression expression = handler.getExpressionParser().parseExpression(securityExpression); + + EvaluationContext evaluationContext = createEvaluationContext(handler, request, response); + evaluationContext.setVariable("id", id); + return ExpressionUtils.evaluateAsBoolean(expression, evaluationContext); + } + + @SuppressWarnings("unchecked") + private EvaluationContext createEvaluationContext(SecurityExpressionHandler handler, HttpServletRequest request, + HttpServletResponse response) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + FilterInvocation filterInvocation = new FilterInvocation(request, response, EMPTY_CHAIN); + + return handler.createEvaluationContext(authentication, filterInvocation); + } + + private SecurityExpressionHandler getFilterSecurityHandler() { + return securityExpressionHandlers.stream() + .filter(handler -> + FilterInvocation.class.equals( + GenericTypeResolver.resolveTypeArgument(handler.getClass(), + SecurityExpressionHandler.class))) + .findAny() + .orElseThrow(() -> new IllegalStateException("No filter invocation security" + + " expression handler has been found! Handlers: " + + securityExpressionHandlers.size())); + } +} \ No newline at end of file diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/AbstractRestProcessingStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/AbstractRestProcessingStep.java index 49f4d818a3..76a250b950 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/AbstractRestProcessingStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/AbstractRestProcessingStep.java @@ -35,6 +35,7 @@ public interface AbstractRestProcessingStep extends ListenerProcessingStep { public static final String UPLOAD_STEP_MOVE_OPERATION_ENTRY = "bitstreammove"; public static final String UPLOAD_STEP_ACCESSCONDITIONS_OPERATION_ENTRY = "accessConditions"; public static final String LICENSE_STEP_OPERATION_ENTRY = "granted"; + public static final String CCLICENSE_STEP_OPERATION_ENTRY = "cclicense/uri"; public static final String UPLOAD_STEP_METADATA_PATH = "metadata"; 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 2840035a7a..0ac468448b 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 @@ -25,6 +25,7 @@ import org.dspace.app.rest.model.CheckSumRest; import org.dspace.app.rest.model.MetadataValueRest; import org.dspace.app.rest.model.UploadBitstreamAccessConditionDTO; import org.dspace.app.rest.model.WorkspaceItemRest; +import org.dspace.app.rest.model.step.DataCCLicense; import org.dspace.app.rest.model.step.DataUpload; import org.dspace.app.rest.model.step.UploadBitstreamRest; import org.dspace.app.rest.projection.Projection; @@ -33,6 +34,8 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.ResourcePolicy; import org.dspace.content.Bitstream; import org.dspace.content.Collection; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; import org.dspace.content.MetadataValue; import org.dspace.content.WorkspaceItem; import org.dspace.content.service.CollectionService; @@ -41,6 +44,7 @@ import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.Utils; +import org.dspace.license.service.CreativeCommonsService; import org.dspace.services.ConfigurationService; import org.dspace.services.RequestService; import org.dspace.services.model.Request; @@ -75,6 +79,8 @@ public class SubmissionService { @Autowired protected WorkflowService workflowService; @Autowired + protected CreativeCommonsService creativeCommonsService; + @Autowired private RequestService requestService; @Autowired private ConverterService converter; @@ -136,19 +142,19 @@ public class SubmissionService { } } -/** - * Build the rest representation of a bitstream as used in the upload section - * ({@link DataUpload}. It contains all its metadata and the list of applied - * access conditions (@link {@link UploadBitstreamAccessConditionDTO} - * - * @param configurationService the DSpace ConfigurationService - * @param source the bitstream to translate in its rest submission - * representation - * @return - * @throws SQLException - */ + /** + * Build the rest representation of a bitstream as used in the upload section + * ({@link DataUpload}. It contains all its metadata and the list of applied + * access conditions (@link {@link UploadBitstreamAccessConditionDTO} + * + * @param configurationService the DSpace ConfigurationService + * @param source the bitstream to translate in its rest submission + * representation + * @return + * @throws SQLException + */ public UploadBitstreamRest buildUploadBitstream(ConfigurationService configurationService, Bitstream source) - throws SQLException { + throws SQLException { UploadBitstreamRest data = new UploadBitstreamRest(); for (MetadataValue md : source.getMetadata()) { @@ -242,7 +248,7 @@ public class SubmissionService { wi = workflowService.start(context, wsi); } catch (IOException e) { throw new RuntimeException("The workflow could not be started for workspaceItem with" + - "id: " + id); + "id: " + id); } return wi; @@ -268,4 +274,27 @@ public class SubmissionService { public void saveWorkflowItem(Context context, XmlWorkflowItem source) throws SQLException, AuthorizeException { workflowItemService.update(context, source); } + + /** + * Builds the CC License data of an inprogress submission based on the cc license info present in the metadata + * + * @param obj - the in progress submission + * @return an object representing the CC License data + * @throws SQLException + * @throws IOException + * @throws AuthorizeException + */ + public DataCCLicense getDataCCLicense(InProgressSubmission obj) + throws SQLException, IOException, AuthorizeException { + DataCCLicense result = new DataCCLicense(); + Item item = obj.getItem(); + + result.setUri(creativeCommonsService.getLicenseURI(item)); + result.setRights(creativeCommonsService.getLicenseName(item)); + + Bitstream licenseRdfBitstream = creativeCommonsService.getLicenseRdfBitstream(item); + result.setFile(converter.toRest(licenseRdfBitstream, Projection.DEFAULT)); + + return result; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CCLicenseAddPatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CCLicenseAddPatchOperation.java new file mode 100644 index 0000000000..e3286551d5 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CCLicenseAddPatchOperation.java @@ -0,0 +1,69 @@ +/** + * 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.submit.factory.impl; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.license.service.CreativeCommonsService; +import org.dspace.services.model.Request; +import org.springframework.beans.factory.annotation.Autowired; + + +/** + * Submission "add" PATCH operation + * + * To add or update the Creative Commons License of a workspace item. + * When the item already has a Creative Commons License, the license will be replaced with a new one. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/submission/workspaceitems/31599 -H "Content-Type: + * application/json" -d '[{ "op": "add", "path": "/sections/cclicense/uri", + * "value":"http://creativecommons.org/licenses/by-nc-sa/3.0/us/"}]' + * + */ +public class CCLicenseAddPatchOperation extends AddPatchOperation { + + @Autowired + CreativeCommonsService creativeCommonsService; + + @Override + protected Class getArrayClassForEvaluation() { + return String[].class; + } + + @Override + protected Class getClassForEvaluation() { + return String.class; + } + + @Override + void add(Context context, Request currentRequest, InProgressSubmission source, String path, Object value) + throws Exception { + + + String licenseUri = null; + if (value instanceof String) { + licenseUri = (String) value; + } + + if (StringUtils.isBlank(licenseUri)) { + throw new IllegalArgumentException( + "Value is not a valid license URI"); + } + + Item item = source.getItem(); + boolean updateLicense = creativeCommonsService.updateLicense(context, licenseUri, item); + if (!updateLicense) { + throw new IllegalArgumentException("The license uri: " + licenseUri + ", could not be resolved to a " + + "CC license"); + } + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CCLicenseRemovePatchOperation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CCLicenseRemovePatchOperation.java new file mode 100644 index 0000000000..add819b7a4 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/factory/impl/CCLicenseRemovePatchOperation.java @@ -0,0 +1,59 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.submit.factory.impl; + +import org.apache.commons.lang3.StringUtils; +import org.dspace.content.InProgressSubmission; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.license.service.CreativeCommonsService; +import org.dspace.services.model.Request; +import org.springframework.beans.factory.annotation.Autowired; + + +/** + * Submission "remove" PATCH operation + * + * To remove the Creative Commons License of a workspace item. + * + * Example: + * curl -X PATCH http://${dspace.server.url}/api/submission/workspaceitems/31599 -H "Content-Type: + * application/json" -d '[{ "op": "remove", "path": "/sections/cclicense/uri"}]' + * + */ +public class CCLicenseRemovePatchOperation extends RemovePatchOperation { + + @Autowired + CreativeCommonsService creativeCommonsService; + + @Override + protected Class getArrayClassForEvaluation() { + return String[].class; + } + + @Override + protected Class getClassForEvaluation() { + return String.class; + } + + @Override + void remove(Context context, Request currentRequest, InProgressSubmission source, String path, Object value) + throws Exception { + Item item = source.getItem(); + + + if (StringUtils.isNotBlank(creativeCommonsService.getLicenseName(item))) { + creativeCommonsService.removeLicense(context, item); + } else { + throw new IllegalArgumentException("No CC license can be removed since none is present on submission: " + + source.getID()); + } + + } + +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/CCLicenseStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/CCLicenseStep.java new file mode 100644 index 0000000000..75f1949116 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/CCLicenseStep.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.submit.step; + +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.model.step.DataCCLicense; +import org.dspace.app.rest.submit.AbstractRestProcessingStep; +import org.dspace.app.rest.submit.SubmissionService; +import org.dspace.app.rest.submit.factory.PatchOperationFactory; +import org.dspace.app.rest.submit.factory.impl.PatchOperation; +import org.dspace.app.util.SubmissionStepConfig; +import org.dspace.content.InProgressSubmission; +import org.dspace.core.Context; +import org.dspace.services.model.Request; + +/** + * CC License step for DSpace Spring Rest. Expose the creative commons license information about the in progress + * submission. + */ +public class CCLicenseStep extends org.dspace.submit.step.CCLicenseStep implements AbstractRestProcessingStep { + + /** + * Retrieves the CC License data of the in progress submission + * + * @param submissionService the submission service + * @param obj the in progress submission + * @param config the submission step configuration + * @return the CC License data of the in progress submission + * @throws Exception + */ + @Override + public DataCCLicense getData(SubmissionService submissionService, InProgressSubmission obj, + SubmissionStepConfig config) + throws Exception { + return submissionService.getDataCCLicense(obj); + } + + + /** + * Processes a patch for the CC License data + * + * @param context the DSpace context + * @param currentRequest the http request + * @param source the in progress submission + * @param op the json patch operation + * @throws Exception + */ + @Override + public void doPatchProcessing(Context context, Request currentRequest, InProgressSubmission source, Operation op) + throws Exception { + + if (op.getPath().endsWith(CCLICENSE_STEP_OPERATION_ENTRY)) { + + PatchOperation patchOperation = new PatchOperationFactory() + .instanceOf(CCLICENSE_STEP_OPERATION_ENTRY, op.getOp()); + patchOperation.perform(context, currentRequest, source, op); + + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java index 3fcdbe8133..305b282fce 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ApplicationConfig.java @@ -36,8 +36,8 @@ public class ApplicationConfig { @Value("${rest.cors.allow-credentials:true}") private boolean corsAllowCredentials; - // Configured User Interface URL (default: http://localhost:3000) - @Value("${dspace.ui.url:http://localhost:3000}") + // Configured User Interface URL (default: http://localhost:4000) + @Value("${dspace.ui.url:http://localhost:4000}") private String uiURL; /** 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 15a0237d82..fc3b5fb711 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 @@ -53,6 +53,7 @@ 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.PropertyRest; import org.dspace.app.rest.model.ResourcePolicyRest; import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.RestModel; @@ -86,6 +87,7 @@ 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.security.access.AccessDeniedException; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; @@ -263,6 +265,9 @@ public class Utils { if (StringUtils.equals(modelPlural, "versionhistories")) { return VersionHistoryRest.NAME; } + if (StringUtils.equals(modelPlural, "properties")) { + return PropertyRest.NAME; + } return modelPlural.replaceAll("s$", ""); } @@ -668,7 +673,13 @@ public class Utils { Object linkedObject = method.invoke(linkRepository, null, contentId, null, projection); resource.embedResource(rel, wrapForEmbedding(resource, linkedObject, link, oldLinks)); } catch (InvocationTargetException e) { - if (e.getTargetException() instanceof RuntimeException) { + // This will be thrown from the LinkRepository if a Resource has been requested that'll try to embed + // something that we don't have READ rights to. It'll then throw an AccessDeniedException from that + // linkRepository and we want to catch it here since we don't want our entire request to fail if a + // subresource of the requested resource is not available to be embedded. Instead we'll log it here + if (e.getTargetException() instanceof AccessDeniedException) { + log.warn("Tried fetching resource: " + linkRest.name() + " for DSpaceObject with ID: " + contentId); + } else if (e.getTargetException() instanceof RuntimeException) { throw (RuntimeException) e.getTargetException(); } else { throw new RuntimeException(e); diff --git a/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml b/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml index fb09e305be..ce9b3ab2a2 100644 --- a/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml +++ b/dspace-server-webapp/src/main/resources/spring/spring-dspace-core-services.xml @@ -56,6 +56,10 @@ + + + @@ -83,6 +87,10 @@ + + + diff --git a/dspace-server-webapp/src/main/webapp/index.html b/dspace-server-webapp/src/main/webapp/index.html index 15661c654a..41bd71c78b 100644 --- a/dspace-server-webapp/src/main/webapp/index.html +++ b/dspace-server-webapp/src/main/webapp/index.html @@ -1,17 +1,17 @@ - - The HAL Browser (customized for Spring Data REST) + The HAL Browser (customized for DSpace Server Webapp) - + - +
@@ -75,7 +76,7 @@ setTimeout(function() { window.location.href = window.location.pathname.replace("login.html", ""); }, 2000); - }; + }; toastr.options = { "closeButton": false, "debug": false, @@ -132,27 +133,25 @@ function capitalizeFirstLetter(string) { return string.charAt(0).toUpperCase() + string.slice(1); } - - $("#login-form").submit(function() { + + $("#login-form").submit(function(event) { + event.preventDefault(); $.ajax({ //This depends on this file to be called login.html url : window.location.href.replace("login.html", "") + 'api/authn/login', type : 'POST', async : false, - data : 'password='+encodeURIComponent($("#password").val())+'&user='+encodeURIComponent($("#username").val()), - headers : { - "Content-Type" : 'application/x-www-form-urlencoded', - "Accept:" : '*/*' + data: { + user: $("#username").val(), + password: $("#password").val() }, success : successHandler, error : function() { toastr.error('The credentials you entered are invalid. Please try again.', 'Login Failed'); } }); - - return false; }); }); - \ No newline at end of file + diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/item-submission.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/item-submission.xml new file mode 100644 index 0000000000..481b508176 --- /dev/null +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/item-submission.xml @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.dspace.app.rest.submit.step.CollectionStep + collection + submission + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + + + submit.progressbar.describe.steptwo + org.dspace.app.rest.submit.step.DescribeStep + submission-form + + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + + + + submit.progressbar.upload + org.dspace.app.rest.submit.step.UploadStep + upload + + + submit.progressbar.license + org.dspace.app.rest.submit.step.LicenseStep + license + submission + + + + + + + + submit.progressbar.CClicense + org.dspace.app.rest.submit.step.CCLicenseStep + cclicense + + + + + + + + + + + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + + + submit.progressbar.describe.stepone + org.dspace.app.rest.submit.step.DescribeStep + submission-form + + + + + Sample + org.dspace.submit.step.SampleStep + sample + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/core-services-mock.xml b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/core-services-mock.xml index ff13e7f6b4..8010d3e5d6 100644 --- a/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/core-services-mock.xml +++ b/dspace-server-webapp/src/test/data/dspaceFolder/config/spring/api/core-services-mock.xml @@ -6,4 +6,5 @@ + diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java index 78657e9c35..089f781902 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorityRestRepositoryIT.java @@ -7,6 +7,8 @@ */ package org.dspace.app.rest; +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +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; @@ -195,6 +197,19 @@ public class AuthorityRestRepositoryIT extends AbstractControllerIntegrationTest .andExpect(jsonPath("$.page.totalElements", Matchers.is(1))); } + @Test + public void discoverableNestedLinkTest() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._links",Matchers.allOf( + hasJsonPath("$.authorizations.href", + is("http://localhost/api/authz/authorizations")), + hasJsonPath("$.authorization-search.href", + is("http://localhost/api/authz/authorization/search")) + ))); + } + @Test public void retrieveSolrValueTest() throws Exception { String token = getAuthToken(admin.getEmail(), password); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationFeatureRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationFeatureRestRepositoryIT.java index a3556ad503..0aadff7a99 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationFeatureRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationFeatureRestRepositoryIT.java @@ -29,7 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired; /** * Test suite for the Authorization Feature endpoint - * + * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ @@ -50,10 +50,10 @@ public class AuthorizationFeatureRestRepositoryIT extends AbstractControllerInte // verify that only the admin can access the endpoint (see subsequent call in the method) getClient(adminToken).perform(get("/api/authz/features")).andExpect(status().isOk()) - .andExpect(jsonPath("$._embedded.features", Matchers.hasSize(is(expReturn)))) - .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/authz/features"))) - .andExpect(jsonPath("$.page.size", is(20))) - .andExpect(jsonPath("$.page.totalElements", is(featuresNum))); + .andExpect(jsonPath("$._embedded.features", Matchers.hasSize(is(expReturn)))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/authz/features"))) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(featuresNum))); // verify that anonymous user cannot access getClient().perform(get("/api/authz/features")).andExpect(status().isUnauthorized()); // verify that normal user cannot access @@ -78,21 +78,21 @@ public class AuthorizationFeatureRestRepositoryIT extends AbstractControllerInte AtomicReference idRef = new AtomicReference(); getClient(adminToken) - .perform(get("/api/authz/features").param("page", String.valueOf(page)).param("size", "1")) - .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.features", Matchers.hasSize(is(1)))) - .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/authz/features"))) - .andExpect( - (page == 0) ? jsonPath("$._links.prev.href").doesNotExist() - : jsonPath("$._links.prev.href", Matchers.containsString("/api/authz/features"))) - .andExpect((page == featuresNum - 1) - ? jsonPath("$._links.next.href").doesNotExist() - : jsonPath("$._links.next.href", Matchers.containsString("/api/authz/features"))) - .andExpect(jsonPath("$._links.first.href", Matchers.containsString("/api/authz/features"))) - .andExpect(jsonPath("$._links.last.href", Matchers.containsString("/api/authz/features"))) - .andExpect(jsonPath("$.page.size", is(1))) - .andExpect(jsonPath("$.page.totalElements", is(Integer.valueOf(featuresNum)))) - .andDo(result -> idRef - .set(read(result.getResponse().getContentAsString(), "$._embedded.features[0].id"))); + .perform(get("/api/authz/features").param("page", String.valueOf(page)).param("size", "1")) + .andExpect(status().isOk()).andExpect(jsonPath("$._embedded.features", Matchers.hasSize(is(1)))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/authz/features"))) + .andExpect( + (page == 0) ? jsonPath("$._links.prev.href").doesNotExist() + : jsonPath("$._links.prev.href", Matchers.containsString("/api/authz/features"))) + .andExpect((page == featuresNum - 1) + ? jsonPath("$._links.next.href").doesNotExist() + : jsonPath("$._links.next.href", Matchers.containsString("/api/authz/features"))) + .andExpect(jsonPath("$._links.first.href", Matchers.containsString("/api/authz/features"))) + .andExpect(jsonPath("$._links.last.href", Matchers.containsString("/api/authz/features"))) + .andExpect(jsonPath("$.page.size", is(1))) + .andExpect(jsonPath("$.page.totalElements", is(Integer.valueOf(featuresNum)))) + .andDo(result -> idRef + .set(read(result.getResponse().getContentAsString(), "$._embedded.features[0].id"))); if (idRef.get() == null || featureIDs.contains(idRef.get())) { fail("Duplicate feature " + idRef.get() + " returned at page " + page); @@ -108,30 +108,17 @@ public class AuthorizationFeatureRestRepositoryIT extends AbstractControllerInte * @throws Exception */ public void findOneTest() throws Exception { - String adminToken = getAuthToken(admin.getEmail(), password); - // verify that only the admin can access the endpoint (see subsequent call in the method) - getClient(adminToken).perform(get("/api/authz/features/withdrawItem")).andExpect(status().isOk()) - .andExpect(jsonPath("$.id", is("withdrawItem"))) - .andExpect(jsonPath("$.description", Matchers.any(String.class))) - .andExpect(jsonPath("$.resourcetypes", Matchers.contains("core.item"))) - .andExpect(jsonPath("$.type", is("feature"))); - // verify that anonymous user cannot access - getClient().perform(get("/api/authz/features/withdrawItem")).andExpect(status().isUnauthorized()); - // verify that normal user cannot access - String epersonAuthToken = getAuthToken(eperson.getEmail(), password); - getClient(epersonAuthToken).perform(get("/api/authz/features/withdrawItem")).andExpect(status().isForbidden()); + getClient().perform(get("/api/authz/features/withdrawItem")).andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is("withdrawItem"))) + .andExpect(jsonPath("$.description", Matchers.any(String.class))) + .andExpect(jsonPath("$.resourcetypes", Matchers.contains("core.item"))) + .andExpect(jsonPath("$.type", is("feature"))); } @Test public void findOneNotFoundTest() throws Exception { - String adminToken = getAuthToken(admin.getEmail(), password); - // verify that only the admin can access the endpoint and get the not found response code - // (see subsequent calls in the method for unauthorized and forbidden attempts) - getClient(adminToken).perform(get("/api/authz/features/not-existing-feature")).andExpect(status().isNotFound()); - // verify that anonymous user cannot access, without information disclosure - getClient().perform(get("/api/authz/features/not-existing-feature")).andExpect(status().isUnauthorized()); - // verify that normal user cannot access, without information disclosure - getClient(adminToken).perform(get("/api/authz/features/1")).andExpect(status().isNotFound()); + getClient().perform(get("/api/authz/features/not-existing-feature")).andExpect(status().isNotFound()); + } @Test @@ -146,27 +133,28 @@ public class AuthorizationFeatureRestRepositoryIT extends AbstractControllerInte for (String type : alwaysTrueFeature.getSupportedTypes()) { // verify that only the admin can access the endpoint (see subsequent call in the method) getClient(adminToken).perform(get("/api/authz/features/search/resourcetype").param("type", type)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", - JsonPathMatchers.hasJsonPath("$._embedded.features", - Matchers.everyItem( - JsonPathMatchers.hasJsonPath("$.resourcetypes", - Matchers.hasItem(is(type)))) - ))) - .andExpect( - jsonPath("$._links.self.href", - Matchers.containsString("/api/authz/features/search/resourcetype"))); + .andExpect(status().isOk()) + .andExpect(jsonPath("$", + JsonPathMatchers.hasJsonPath("$._embedded.features", + Matchers.everyItem( + JsonPathMatchers.hasJsonPath( + "$.resourcetypes", + Matchers.hasItem(is(type)))) + ))) + .andExpect( + jsonPath("$._links.self.href", + Matchers.containsString("/api/authz/features/search/resourcetype"))); } // verify that the right response code is returned also for not existing types getClient(adminToken).perform(get("/api/authz/features/search/resourcetype").param("type", "NOT-EXISTING")) - .andExpect(status().isOk()).andExpect(jsonPath("$.page.totalElements", is(0))); + .andExpect(status().isOk()).andExpect(jsonPath("$.page.totalElements", is(0))); // verify that anonymous user cannot access, without information disclosure getClient().perform(get("/api/authz/features/search/resourcetype").param("type", "core.item")) - .andExpect(status().isUnauthorized()); + .andExpect(status().isUnauthorized()); // verify that normal user cannot access, without information disclosure String epersonAuthToken = getAuthToken(eperson.getEmail(), password); getClient(epersonAuthToken).perform(get("/api/authz/features/search/resourcetype").param("type", "core.item")) - .andExpect(status().isForbidden()); + .andExpect(status().isForbidden()); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationFeatureServiceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationFeatureServiceIT.java index 58b5d1a730..d53bdc92c8 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationFeatureServiceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationFeatureServiceIT.java @@ -22,7 +22,7 @@ import org.dspace.app.rest.authorization.AlwaysTrueFeature; import org.dspace.app.rest.authorization.AuthorizationFeature; import org.dspace.app.rest.authorization.AuthorizationFeatureService; import org.dspace.app.rest.authorization.TrueForAdminsFeature; -import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.converter.SiteConverter; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.SiteRest; import org.dspace.app.rest.projection.DefaultProjection; @@ -60,7 +60,7 @@ public class AuthorizationFeatureServiceIT extends AbstractIntegrationTestWithDa private SiteService siteService; @Autowired - private ConverterService converterService; + private SiteConverter siteConverter; @Autowired private AuthorizationFeatureService authzFeatureService; @@ -143,7 +143,7 @@ public class AuthorizationFeatureServiceIT extends AbstractIntegrationTestWithDa */ public void isAuthorizedTest() throws Exception { Site site = siteService.findSite(context); - SiteRest siteRest = converterService.toRest(site, DefaultProjection.DEFAULT); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); AuthorizationFeature alwaysTrue = authzFeatureService.find(AlwaysTrueFeature.NAME); AuthorizationFeature alwaysFalse = authzFeatureService.find(AlwaysFalseFeature.NAME); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java index 65f062bbf2..05631790e3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/AuthorizationRestRepositoryIT.java @@ -31,7 +31,9 @@ import org.dspace.app.rest.authorization.TrueForUsersInGroupTestFeature; import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.builder.EPersonBuilder; import org.dspace.app.rest.builder.GroupBuilder; -import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.converter.CommunityConverter; +import org.dspace.app.rest.converter.EPersonConverter; +import org.dspace.app.rest.converter.SiteConverter; import org.dspace.app.rest.matcher.AuthorizationMatcher; import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.app.rest.model.CommunityRest; @@ -68,8 +70,13 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration private AuthorizationFeatureService authorizationFeatureService; @Autowired - private ConverterService converterService; + private SiteConverter siteConverter; + @Autowired + private EPersonConverter ePersonConverter; + + @Autowired + private CommunityConverter communityConverter; @Autowired private ConfigurationService configurationService; @@ -148,7 +155,7 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration */ public void findOneTest() throws Exception { Site site = siteService.findSite(context); - SiteRest siteRest = converterService.toRest(site, DefaultProjection.DEFAULT); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); // define three authorizations that we know must exists Authorization authAdminSite = new Authorization(admin, trueForAdmins, siteRest); @@ -190,7 +197,7 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration */ public void findOneUnauthorizedTest() throws Exception { Site site = siteService.findSite(context); - SiteRest siteRest = converterService.toRest(site, DefaultProjection.DEFAULT); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); // define two authorizations that we know must exists Authorization authAdminSite = new Authorization(admin, alwaysTrue, siteRest); @@ -214,7 +221,7 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration public void findOneForbiddenTest() throws Exception { context.turnOffAuthorisationSystem(); Site site = siteService.findSite(context); - SiteRest siteRest = converterService.toRest(site, DefaultProjection.DEFAULT); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); EPerson testEPerson = EPersonBuilder.createEPerson(context) .withEmail("test-authorization@example.com") .withPassword(password).build(); @@ -250,8 +257,8 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration public void findOneNotFoundTest() throws Exception { context.turnOffAuthorisationSystem(); Site site = siteService.findSite(context); - SiteRest siteRest = converterService.toRest(site, DefaultProjection.DEFAULT); - EPersonRest epersonRest = converterService.toRest(eperson, DefaultProjection.DEFAULT); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); + EPersonRest epersonRest = ePersonConverter.convert(eperson, DefaultProjection.DEFAULT); context.restoreAuthSystemState(); String epersonToken = getAuthToken(eperson.getEmail(), password); @@ -336,7 +343,7 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration */ public void findOneInternalServerErrorTest() throws Exception { Site site = siteService.findSite(context); - SiteRest siteRest = converterService.toRest(site, DefaultProjection.DEFAULT); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); // define two authorizations that we know will throw exceptions Authorization authAdminSite = new Authorization(admin, alwaysException, siteRest); Authorization authNormalUserSite = new Authorization(eperson, alwaysException, siteRest); @@ -363,7 +370,7 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration */ public void findByObjectTest() throws Exception { Site site = siteService.findSite(context); - SiteRest siteRest = converterService.toRest(site, converterService.getProjection(DefaultProjection.NAME)); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); String siteUri = utils.linkToSingleResource(siteRest, "self").getHref(); // disarm the alwaysThrowExceptionFeature @@ -699,7 +706,7 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration */ public void findByObjectUnauthorizedTest() throws Exception { Site site = siteService.findSite(context); - SiteRest siteRest = converterService.toRest(site, converterService.getProjection(DefaultProjection.NAME)); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); String siteUri = utils.linkToSingleResource(siteRest, "self").getHref(); // disarm the alwaysThrowExceptionFeature @@ -726,7 +733,7 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration */ public void findByObjectForbiddenTest() throws Exception { Site site = siteService.findSite(context); - SiteRest siteRest = converterService.toRest(site, converterService.getProjection(DefaultProjection.NAME)); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); String siteUri = utils.linkToSingleResource(siteRest, "self").getHref(); context.turnOffAuthorisationSystem(); EPerson anotherEperson = EPersonBuilder.createEPerson(context).withEmail("another@example.com") @@ -755,7 +762,7 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration */ public void findByObjectInternalServerErrorTest() throws Exception { Site site = siteService.findSite(context); - SiteRest siteRest = converterService.toRest(site, converterService.getProjection(DefaultProjection.NAME)); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); String siteUri = utils.linkToSingleResource(siteRest, "self").getHref(); // verify that it works for administrators @@ -800,7 +807,7 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration public void findByObjectAndFeatureTest() throws Exception { context.turnOffAuthorisationSystem(); Community com = CommunityBuilder.createCommunity(context).withName("A test community").build(); - CommunityRest comRest = converterService.toRest(com, converterService.getProjection(DefaultProjection.NAME)); + CommunityRest comRest = communityConverter.convert(com, DefaultProjection.DEFAULT); String comUri = utils.linkToSingleResource(comRest, "self").getHref(); context.restoreAuthSystemState(); @@ -878,7 +885,7 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration */ public void findByObjectAndFeatureNotGrantedTest() throws Exception { Site site = siteService.findSite(context); - SiteRest siteRest = converterService.toRest(site, converterService.getProjection(DefaultProjection.NAME)); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); String siteUri = utils.linkToSingleResource(siteRest, "self").getHref(); // verify that it works for administrators @@ -927,7 +934,7 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration public void findByNotExistingObjectAndFeatureTest() throws Exception { String wrongSiteUri = "http://localhost/api/core/sites/" + UUID.randomUUID(); Site site = siteService.findSite(context); - SiteRest siteRest = converterService.toRest(site, converterService.getProjection(DefaultProjection.NAME)); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); String siteUri = utils.linkToSingleResource(siteRest, "self").getHref(); // disarm the alwaysThrowExceptionFeature @@ -1011,7 +1018,7 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration "http://localhost/api/core/sites/this-is-not-an-uuid" }; Site site = siteService.findSite(context); - SiteRest siteRest = converterService.toRest(site, converterService.getProjection(DefaultProjection.NAME)); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); String siteUri = utils.linkToSingleResource(siteRest, "self").getHref(); // disarm the alwaysThrowExceptionFeature configurationService.setProperty("org.dspace.app.rest.authorization.AlwaysThrowExceptionFeature.turnoff", true); @@ -1096,7 +1103,7 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration */ public void findByObjectAndFeatureUnauthorizedTest() throws Exception { Site site = siteService.findSite(context); - SiteRest siteRest = converterService.toRest(site, converterService.getProjection(DefaultProjection.NAME)); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); String siteUri = utils.linkToSingleResource(siteRest, "self").getHref(); // disarm the alwaysThrowExceptionFeature @@ -1125,7 +1132,7 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration */ public void findByObjectAndFeatureForbiddenTest() throws Exception { Site site = siteService.findSite(context); - SiteRest siteRest = converterService.toRest(site, converterService.getProjection(DefaultProjection.NAME)); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); String siteUri = utils.linkToSingleResource(siteRest, "self").getHref(); context.turnOffAuthorisationSystem(); EPerson anotherEperson = EPersonBuilder.createEPerson(context).withEmail("another@example.com") @@ -1156,7 +1163,7 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration */ public void findByObjectAndFeatureInternalServerErrorTest() throws Exception { Site site = siteService.findSite(context); - SiteRest siteRest = converterService.toRest(site, converterService.getProjection(DefaultProjection.NAME)); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); String siteUri = utils.linkToSingleResource(siteRest, "self").getHref(); // verify that it works for administrators @@ -1192,7 +1199,7 @@ public class AuthorizationRestRepositoryIT extends AbstractControllerIntegration */ public void verifySpecialGroupMembershipTest() throws Exception { Site site = siteService.findSite(context); - SiteRest siteRest = converterService.toRest(site, converterService.getProjection(DefaultProjection.NAME)); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); String siteUri = utils.linkToSingleResource(siteRest, "self").getHref(); context.turnOffAuthorisationSystem(); // create two normal users and put one in the test group directly 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 ae90b486b1..744e673912 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,7 +27,7 @@ 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.ConverterService; +import org.dspace.app.rest.converter.BitstreamFormatConverter; import org.dspace.app.rest.matcher.BitstreamFormatMatcher; import org.dspace.app.rest.matcher.HalMatcher; import org.dspace.app.rest.model.BitstreamFormatRest; @@ -51,10 +51,10 @@ import org.springframework.test.web.servlet.MvcResult; public class BitstreamFormatRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired - ConverterService converter; + BitstreamFormatService bitstreamFormatService; @Autowired - BitstreamFormatService bitstreamFormatService; + private BitstreamFormatConverter bitstreamFormatConverter; private final int DEFAULT_AMOUNT_FORMATS = 80; @@ -284,7 +284,7 @@ public class BitstreamFormatRestRepositoryIT extends AbstractControllerIntegrati .build(); context.restoreAuthSystemState(); - BitstreamFormatRest bitstreamFormatRest = converter.toRest(bitstreamFormat, Projection.DEFAULT); + BitstreamFormatRest bitstreamFormatRest = bitstreamFormatConverter.convert(bitstreamFormat, Projection.DEFAULT); String token = getAuthToken(admin.getEmail(), password); //Update it bitstreamFormatRest.setShortDescription("Test short UPDATED"); @@ -316,7 +316,7 @@ public class BitstreamFormatRestRepositoryIT extends AbstractControllerIntegrati .build(); context.restoreAuthSystemState(); - BitstreamFormatRest bitstreamFormatRest = converter.toRest(bitstreamFormat, Projection.DEFAULT); + BitstreamFormatRest bitstreamFormatRest = bitstreamFormatConverter.convert(bitstreamFormat, Projection.DEFAULT); String token = getAuthToken(admin.getEmail(), password); //Update it bitstreamFormatRest.setShortDescription("Test short UPDATED"); @@ -354,7 +354,7 @@ public class BitstreamFormatRestRepositoryIT extends AbstractControllerIntegrati int nonExistentBitstreamFormatID = 404404404; - BitstreamFormatRest bitstreamFormatRest = converter.toRest(bitstreamFormat, Projection.DEFAULT); + BitstreamFormatRest bitstreamFormatRest = bitstreamFormatConverter.convert(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"); @@ -391,7 +391,7 @@ public class BitstreamFormatRestRepositoryIT extends AbstractControllerIntegrati int nonExistentBitstreamFormatID = 404404404; - BitstreamFormatRest bitstreamFormatRest = converter.toRest(bitstreamFormat, Projection.DEFAULT); + BitstreamFormatRest bitstreamFormatRest = bitstreamFormatConverter.convert(bitstreamFormat, Projection.DEFAULT); String token = getAuthToken(admin.getEmail(), password); //Update it with non existent ID in URL bitstreamFormatRest.setShortDescription("Test short UPDATED"); @@ -427,7 +427,7 @@ public class BitstreamFormatRestRepositoryIT extends AbstractControllerIntegrati int nonExistentBitstreamFormatID = 404404404; - BitstreamFormatRest bitstreamFormatRest = converter.toRest(bitstreamFormat, Projection.DEFAULT); + BitstreamFormatRest bitstreamFormatRest = bitstreamFormatConverter.convert(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"); @@ -465,7 +465,8 @@ public class BitstreamFormatRestRepositoryIT extends AbstractControllerIntegrati .build(); context.restoreAuthSystemState(); - BitstreamFormatRest bitstreamFormatRest = converter.toRest(bitstreamFormat1, Projection.DEFAULT); + BitstreamFormatRest bitstreamFormatRest = bitstreamFormatConverter.convert(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"); @@ -498,7 +499,7 @@ public class BitstreamFormatRestRepositoryIT extends AbstractControllerIntegrati .build(); context.restoreAuthSystemState(); - BitstreamFormatRest bitstreamFormatRest = converter.toRest(bitstreamFormat, Projection.DEFAULT); + BitstreamFormatRest bitstreamFormatRest = bitstreamFormatConverter.convert(bitstreamFormat, Projection.DEFAULT); //Try to update bitstreamFormat without auth token bitstreamFormatRest.setShortDescription("Test short UPDATED"); @@ -534,7 +535,7 @@ public class BitstreamFormatRestRepositoryIT extends AbstractControllerIntegrati .build(); context.restoreAuthSystemState(); - BitstreamFormatRest bitstreamFormatRest = converter.toRest(bitstreamFormat, Projection.DEFAULT); + BitstreamFormatRest bitstreamFormatRest = bitstreamFormatConverter.convert(bitstreamFormat, Projection.DEFAULT); String token = getAuthToken(user.getEmail(), password); //Try to update bitstreamFormat without non-admin auth token diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java index 6f1b378978..34bb56cbec 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java @@ -33,13 +33,14 @@ import org.dspace.app.rest.matcher.BitstreamMatcher; import org.dspace.app.rest.matcher.HalMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; +import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.content.service.BitstreamService; +import org.dspace.core.Constants; import org.dspace.eperson.EPerson; -import org.hamcrest.Matchers; import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -49,6 +50,9 @@ public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest @Autowired private BitstreamService bitstreamService; + @Autowired + private ResourcePolicyService resourcePolicyService; + @Test public void findAllTest() throws Exception { //We turn off the authorization system in order to create the structure as defined below @@ -99,99 +103,8 @@ public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest String token = getAuthToken(admin.getEmail(), password); - getClient(token).perform(get("/api/core/bitstreams/") - .param("projection", "full")) - .andExpect(status().isOk()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.bitstreams", Matchers.containsInAnyOrder( - BitstreamMatcher.matchBitstreamEntry(bitstream), - BitstreamMatcher.matchBitstreamEntry(bitstream1) - ))); - } - - @Test - public void findAllPaginationTest() 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"; - //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("descr") - .withMimeType("text/plain") - .build(); - } - - //Add a bitstream to an item - Bitstream bitstream1 = null; - try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { - bitstream1 = BitstreamBuilder. - createBitstream(context, publicItem1, is) - .withName("Bitstream1") - .withDescription("desscrip1") - .withMimeType("text/plain") - .build(); - } - - context.restoreAuthSystemState(); - - String token = getAuthToken(admin.getEmail(), password); - - getClient(token).perform(get("/api/core/bitstreams/") - .param("size", "1") - .param("projection", "full")) - .andExpect(status().isOk()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.bitstreams", Matchers.contains( - BitstreamMatcher.matchBitstreamEntry(bitstream)) - )) - .andExpect(jsonPath("$._embedded.bitstreams", Matchers.not( - Matchers.contains( - BitstreamMatcher.matchBitstreamEntry(bitstream1)) - ) - )) - - ; - - getClient(token).perform(get("/api/core/bitstreams/") - .param("size", "1") - .param("page", "1") - .param("projection", "full")) - .andExpect(status().isOk()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.bitstreams", Matchers.contains( - BitstreamMatcher.matchBitstreamEntry(bitstream1) - ))) - .andExpect(jsonPath("$._embedded.bitstreams", Matchers.not( - Matchers.contains( - BitstreamMatcher.matchBitstreamEntry(bitstream) - ) - ))); - - getClient().perform(get("/api/core/bitstreams/")) - .andExpect(status().isUnauthorized()); + getClient(token).perform(get("/api/core/bitstreams/")) + .andExpect(status().isMethodNotAllowed()); } //TODO Re-enable test after https://jira.duraspace.org/browse/DS-3774 is fixed @@ -322,6 +235,352 @@ public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest } + @Test + public void findOneBitstreamTest_EmbargoedBitstream_Anon() throws Exception { + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + // a public item with an embargoed bitstream + String bitstreamContent = "Embargoed!"; + + Item publicItem1; + Bitstream bitstream; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, org.apache.commons.lang3.CharEncoding.UTF_8)) { + + publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .build(); + + bitstream = BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Test Embargoed Bitstream") + .withDescription("This bitstream is embargoed") + .withMimeType("text/plain") + .withEmbargoPeriod("3 months") + .build(); + } + context.restoreAuthSystemState(); + + // Bitstream metadata should still be accessible by anonymous request + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", BitstreamMatcher.matchProperties(bitstream))) + .andExpect(jsonPath("$", HalMatcher.matchNoEmbeds())) + .andExpect(jsonPath("$", BitstreamMatcher.matchLinks(bitstream.getID()))) + ; + + // Also accessible as embedded object by anonymous request + getClient().perform(get("/api/core/items/" + publicItem1.getID() + "?embed=bundles/bitstreams")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.bundles._embedded.bundles[0]._embedded.bitstreams._embedded" + + ".bitstreams[0]", BitstreamMatcher.matchProperties(bitstream))) + ; + } + + @Test + public void findOneBitstreamTest_NoReadPolicyOnBitstream_Anon() 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"; + + //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(); + } + + // Remove all READ policies on bitstream + resourcePolicyService.removePolicies(context, bitstream, Constants.READ); + + context.restoreAuthSystemState(); + + // Bitstream metadata should still be accessible by anonymous request + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", BitstreamMatcher.matchProperties(bitstream))) + .andExpect(jsonPath("$", HalMatcher.matchNoEmbeds())) + .andExpect(jsonPath("$", BitstreamMatcher.matchLinks(bitstream.getID()))) + ; + + // Also accessible as embedded object by anonymous request + getClient().perform(get("/api/core/items/" + publicItem1.getID() + "?embed=bundles/bitstreams")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.bundles._embedded.bundles[0]._embedded.bitstreams._embedded" + + ".bitstreams[0]", BitstreamMatcher.matchProperties(bitstream))) + ; + } + + @Test + public void findOneBitstreamTest_EmbargoedBitstream_NoREADRightsOnBundle() throws Exception { + context.turnOffAuthorisationSystem(); + context.setCurrentUser(eperson); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + // a public item with an embargoed bitstream + String bitstreamContent = "Embargoed!"; + + Item publicItem1; + Bitstream bitstream; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, org.apache.commons.lang3.CharEncoding.UTF_8)) { + + publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .build(); + + bitstream = BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Test Embargoed Bitstream") + .withDescription("This bitstream is embargoed") + .withMimeType("text/plain") + .withEmbargoPeriod("3 months") + .build(); + } + + // Remove read policies on bundle of bitstream + resourcePolicyService.removePolicies(context, bitstream.getBundles().get(0), Constants.READ); + + context.restoreAuthSystemState(); + + // Bitstream metadata should not be accessible by anonymous request + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID())) + .andExpect(status().isUnauthorized()) + ; + + // Bitstream metadata should not be accessible by submitter + String submitterToken = getAuthToken(context.getCurrentUser().getEmail(), password); + getClient(submitterToken).perform(get("/api/core/bitstreams/" + bitstream.getID())) + .andExpect(status().isForbidden()) + ; + + // Bitstream metadata should be accessible by admin + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/core/bitstreams/" + bitstream.getID())) + .andExpect(status().isOk()) + ; + } + + @Test + public void findOneBitstreamTest_EmbargoedBitstream_ePersonREADRightsOnBundle() throws Exception { + context.turnOffAuthorisationSystem(); + context.setCurrentUser(eperson); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + // a public item with an embargoed bitstream + String bitstreamContent = "Embargoed!"; + + Item publicItem1; + Bitstream bitstream; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, org.apache.commons.lang3.CharEncoding.UTF_8)) { + + publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .build(); + + bitstream = BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Test Embargoed Bitstream") + .withDescription("This bitstream is embargoed") + .withMimeType("text/plain") + .withEmbargoPeriod("3 months") + .build(); + } + + // Replace anon read policy on bundle of bitstream with ePerson READ policy + resourcePolicyService.removePolicies(context, bitstream.getBundles().get(0), Constants.READ); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(eperson) + .withAction(Constants.READ) + .withDspaceObject(bitstream.getBundles().get(0)).build(); + + context.restoreAuthSystemState(); + + // Bitstream metadata should not be accessible by anonymous request + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID())) + .andExpect(status().isUnauthorized()) + ; + + // Bitstream metadata should be accessible by eperson + String submitterToken = getAuthToken(context.getCurrentUser().getEmail(), password); + getClient(submitterToken).perform(get("/api/core/bitstreams/" + bitstream.getID())) + .andExpect(status().isOk()) + ; + + // Bitstream metadata should be accessible by admin + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/core/bitstreams/" + bitstream.getID())) + .andExpect(status().isOk()) + ; + } + + @Test + public void findOneBitstreamTest_EmbargoedBitstream_NoREADRightsOnItem() throws Exception { + context.turnOffAuthorisationSystem(); + context.setCurrentUser(eperson); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + // a public item with an embargoed bitstream + String bitstreamContent = "Embargoed!"; + + Item publicItem1; + Bitstream bitstream; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, org.apache.commons.lang3.CharEncoding.UTF_8)) { + + publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .build(); + + bitstream = BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Test Embargoed Bitstream") + .withDescription("This bitstream is embargoed") + .withMimeType("text/plain") + .withEmbargoPeriod("3 months") + .build(); + } + + // Remove read policies on item of bitstream + resourcePolicyService.removePolicies(context, publicItem1, Constants.READ); + + context.restoreAuthSystemState(); + + // Bitstream metadata should not be accessible by anonymous request + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID())) + .andExpect(status().isUnauthorized()) + ; + + // Bitstream metadata should not be accessible by submitter + String submitterToken = getAuthToken(context.getCurrentUser().getEmail(), password); + getClient(submitterToken).perform(get("/api/core/bitstreams/" + bitstream.getID())) + .andExpect(status().isForbidden()) + ; + + // Bitstream metadata should be accessible by admin + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/core/bitstreams/" + bitstream.getID())) + .andExpect(status().isOk()) + ; + } + + @Test + public void findOneBitstreamTest_EmbargoedBitstream_ePersonREADRightsOnItem() throws Exception { + context.turnOffAuthorisationSystem(); + context.setCurrentUser(eperson); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 1") + .build(); + + // a public item with an embargoed bitstream + String bitstreamContent = "Embargoed!"; + + Item publicItem1; + Bitstream bitstream; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, org.apache.commons.lang3.CharEncoding.UTF_8)) { + + publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald") + .build(); + + bitstream = BitstreamBuilder + .createBitstream(context, publicItem1, is) + .withName("Test Embargoed Bitstream") + .withDescription("This bitstream is embargoed") + .withMimeType("text/plain") + .withEmbargoPeriod("3 months") + .build(); + } + + // Replace anon read policy on item of bitstream with ePerson READ policy + resourcePolicyService.removePolicies(context, publicItem1, Constants.READ); + ResourcePolicyBuilder.createResourcePolicy(context).withUser(eperson) + .withAction(Constants.READ) + .withDspaceObject(publicItem1).build(); + + context.restoreAuthSystemState(); + + // Bitstream metadata should not be accessible by anonymous request + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID())) + .andExpect(status().isUnauthorized()) + ; + + // Bitstream metadata should be accessible by eperson + String submitterToken = getAuthToken(context.getCurrentUser().getEmail(), password); + getClient(submitterToken).perform(get("/api/core/bitstreams/" + bitstream.getID())) + .andExpect(status().isOk()) + ; + + // Bitstream metadata should be accessible by admin + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/core/bitstreams/" + bitstream.getID())) + .andExpect(status().isOk()) + ; + } + @Test public void findOneBitstreamRelsTest() throws Exception { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java index 79e943c2a3..88ca72b08a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java @@ -368,23 +368,18 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) - //We expect only the two public items and the embargoed item to be present .andExpect(jsonPath("$.page.size", is(20))) - .andExpect(jsonPath("$.page.totalElements", is(3))) + .andExpect(jsonPath("$.page.totalElements", is(2))) .andExpect(jsonPath("$.page.totalPages", is(1))) .andExpect(jsonPath("$.page.number", is(0))) - //Verify that the title of the public and embargoed items are present and sorted descending .andExpect(jsonPath("$._embedded.items", contains(ItemMatcher.matchItemWithTitleAndDateIssued(publicItem2, "Public item 2", "2016-02-13"), ItemMatcher.matchItemWithTitleAndDateIssued(publicItem1, "Public item 1", - "2017-10-17"), - ItemMatcher.matchItemWithTitleAndDateIssued(embargoedItem, - "An embargoed publication", - "2017-08-10")))) + "2017-10-17")))) //The private and internal items must not be present .andExpect(jsonPath("$._embedded.items[*].metadata", Matchers.allOf( diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CCLicenseAddPatchOperationIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CCLicenseAddPatchOperationIT.java new file mode 100644 index 0000000000..4f9c753047 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CCLicenseAddPatchOperationIT.java @@ -0,0 +1,125 @@ +/** + * 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.allOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +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.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.ArrayList; +import java.util.List; +import javax.ws.rs.core.MediaType; + +import org.dspace.app.rest.builder.CollectionBuilder; +import org.dspace.app.rest.builder.CommunityBuilder; +import org.dspace.app.rest.builder.WorkspaceItemBuilder; +import org.dspace.app.rest.model.patch.AddOperation; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.WorkspaceItem; +import org.junit.Test; + +/** + * Class to test the methods from the CCLicenseAddPatchOperation + * Since the CC Licenses are obtained from the CC License API, a mock service has been implemented + * This mock service will return a fixed set of CC Licenses using a similar structure to the ones obtained from the + * CC License API. + * Refer to {@link org.dspace.license.MockCCLicenseConnectorServiceImpl} for more information + */ +public class CCLicenseAddPatchOperationIT extends AbstractControllerIntegrationTest { + + + @Test + public void patchSubmissionCCLicense() throws Exception { + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("Community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("Collection") + .build(); + + WorkspaceItem workspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item") + .build(); + + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + + List ops = new ArrayList(); + AddOperation addOperation = new AddOperation("/sections/cclicense/uri", + "http://creativecommons.org/licenses/by-nc-sa/4.0/"); + + ops.add(addOperation); + String patchBody = getPatchContent(ops); + + + getClient(adminToken).perform(patch("/api/submission/workspaceitems/" + workspaceItem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.cclicense", allOf( + hasJsonPath("$.uri", is("http://creativecommons.org/licenses/by-nc-sa/4.0/")), + hasJsonPath("$.rights", + is("Attribution-NonCommercial-ShareAlike 4.0 International")), + hasJsonPath("$.file.name", is("license_rdf")) + ))); + } + + @Test + public void patchSubmissionCCLicenseInvalid() throws Exception { + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("Community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("Collection") + .build(); + + WorkspaceItem workspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item") + .build(); + + context.restoreAuthSystemState(); + + + String adminToken = getAuthToken(admin.getEmail(), password); + + List ops = new ArrayList(); + AddOperation addOperation = new AddOperation("/sections/cclicense/uri", "invalid-license-uri"); + + ops.add(addOperation); + String patchBody = getPatchContent(ops); + + + getClient(adminToken).perform(patch("/api/submission/workspaceitems/" + workspaceItem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isInternalServerError()); + + getClient(adminToken).perform(get("/api/submission/workspaceitems/" + workspaceItem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections", not(hasJsonPath("cclicense")))); + + + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CCLicenseRemovePatchOperationIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CCLicenseRemovePatchOperationIT.java new file mode 100644 index 0000000000..3b05621f08 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CCLicenseRemovePatchOperationIT.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; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.ArrayList; +import java.util.List; +import javax.ws.rs.core.MediaType; + +import org.dspace.app.rest.builder.CollectionBuilder; +import org.dspace.app.rest.builder.CommunityBuilder; +import org.dspace.app.rest.builder.WorkspaceItemBuilder; +import org.dspace.app.rest.model.patch.AddOperation; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.model.patch.RemoveOperation; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.WorkspaceItem; +import org.junit.Test; + +/** + * Class to test the methods from the CCLicenseRemovePatchOperation + * Since the CC Licenses are obtained from the CC License API, a mock service has been implemented + * This mock service will return a fixed set of CC Licenses using a similar structure to the ones obtained from the + * CC License API. + * Refer to {@link org.dspace.license.MockCCLicenseConnectorServiceImpl} for more information + */ +public class CCLicenseRemovePatchOperationIT extends AbstractControllerIntegrationTest { + + + @Test + public void patchRemoveSubmissionCCLicense() throws Exception { + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("Community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("Collection") + .build(); + + WorkspaceItem workspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item") + .build(); + + context.restoreAuthSystemState(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + + // First add a license and verify it is added + List ops = new ArrayList(); + AddOperation addOperation = new AddOperation("/sections/cclicense/uri", + "http://creativecommons.org/licenses/by-nc-sa/4.0/"); + + ops.add(addOperation); + String patchBody = getPatchContent(ops); + + + getClient(epersonToken).perform(patch("/api/submission/workspaceitems/" + workspaceItem.getID()) + .content(patchBody) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections.cclicense", allOf( + hasJsonPath("$.uri", is("http://creativecommons.org/licenses/by-nc-sa/4.0/")), + hasJsonPath("$.rights", + is("Attribution-NonCommercial-ShareAlike 4.0 International")), + hasJsonPath("$.file.name", is("license_rdf")) + ))); + + + // Remove the license again and verify it is removed + + List removeOps = new ArrayList(); + RemoveOperation removeOperation = new RemoveOperation("/sections/cclicense/uri"); + + removeOps.add(removeOperation); + String removePatch = getPatchContent(removeOps); + + + getClient(epersonToken).perform(patch("/api/submission/workspaceitems/" + workspaceItem.getID()) + .content(removePatch) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.sections", not(hasJsonPath("cclicense")))); + } + + + @Test + public void patchRemoveSubmissionCCLicenseNonExisting() throws Exception { + context.turnOffAuthorisationSystem(); + + Community community = CommunityBuilder.createCommunity(context) + .withName("Community") + .build(); + + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("Collection") + .build(); + + WorkspaceItem workspaceItem = WorkspaceItemBuilder.createWorkspaceItem(context, collection) + .withTitle("Workspace Item") + .build(); + + context.restoreAuthSystemState(); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + + + List removeOps = new ArrayList(); + RemoveOperation removeOperation = new RemoveOperation("/sections/cclicense/uri"); + + removeOps.add(removeOperation); + String removePatch = getPatchContent(removeOps); + + + getClient(epersonToken).perform(patch("/api/submission/workspaceitems/" + workspaceItem.getID()) + .content(removePatch) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isInternalServerError()); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionGroupRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionGroupRestControllerIT.java index dd13ef2a6e..7464e9c38c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionGroupRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CollectionGroupRestControllerIT.java @@ -1111,7 +1111,6 @@ public class CollectionGroupRestControllerIT extends AbstractControllerIntegrati } - @Test public void postCollectionDefaultItemReadGroupCreateDefaultItemReadGroupSuccessParentCommunityAdmin() throws Exception { @@ -1600,7 +1599,6 @@ public class CollectionGroupRestControllerIT extends AbstractControllerIntegrati } - @Test public void postCollectionDefaultBitstreamReadGroupCreateDefaultBitstreamReadGroupSuccessParentCommunityAdmin() throws Exception { 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 3236ba03e6..62dc114c6e 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 @@ -30,8 +30,9 @@ 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.builder.EPersonBuilder; +import org.dspace.app.rest.builder.GroupBuilder; import org.dspace.app.rest.builder.ResourcePolicyBuilder; -import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.converter.CollectionConverter; import org.dspace.app.rest.matcher.CollectionMatcher; import org.dspace.app.rest.matcher.CommunityMatcher; import org.dspace.app.rest.matcher.HalMatcher; @@ -49,6 +50,8 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.core.Constants; import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; import org.hamcrest.Matchers; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -57,7 +60,7 @@ import org.springframework.http.MediaType; public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired - ConverterService converter; + CollectionConverter collectionConverter; @Autowired AuthorizeService authorizeService; @@ -65,6 +68,9 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes @Autowired ResourcePolicyService resoucePolicyService; + @Autowired + GroupService groupService; + @Test public void findAllTest() throws Exception { @@ -466,7 +472,7 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes context.restoreAuthSystemState(); - getClient().perform(get("/api/core/collections/search/findAuthorized")) + getClient().perform(get("/api/core/collections/search/findSubmitAuthorized")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.page.totalElements", is(0))) @@ -475,6 +481,235 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes } + @Test + public void findAuthorizedCollectionsTest() throws Exception { + + context.turnOffAuthorisationSystem(); + EPerson eperson2 = EPersonBuilder.createEPerson(context) + .withEmail("eperson2@example.com") + .withPassword(password) + .build(); + + 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) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .build(); + Collection col2 = CollectionBuilder.createCollection(context, child2) + .withName("Collection 2") + .build(); + Collection col3 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection 3") + .withSubmitterGroup(eperson) + .build(); + + Group ChildGroupOfSubmitterGroup = GroupBuilder.createGroup(context) + .withName("Child group of submitters") + .withParent(col1.getSubmitters()) + .addMember(eperson2) + .build(); + + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + String tokenEPerson2 = getAuthToken(eperson2.getEmail(), password); + + getClient(tokenEPerson).perform(get("/api/core/collections/search/findSubmitAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.collections", Matchers.containsInAnyOrder( + CollectionMatcher.matchProperties(col1.getName(), col1.getID(), col1.getHandle()), + CollectionMatcher.matchProperties(col3.getName(), col3.getID(), col3.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + getClient(tokenEPerson2).perform(get("/api/core/collections/search/findSubmitAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.collections", Matchers.contains( + CollectionMatcher.matchProperties(col1.getName(), col1.getID(), col1.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/collections/search/findSubmitAuthorized")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.collections", Matchers.containsInAnyOrder( + CollectionMatcher.matchProperties(col1.getName(), col1.getID(), col1.getHandle()), + CollectionMatcher.matchProperties(col2.getName(), col2.getID(), col2.getHandle()), + CollectionMatcher.matchProperties(col3.getName(), col3.getID(), col3.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + } + + @Test + public void findAuthorizedCollectionsWithQueryTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + EPerson eperson2 = EPersonBuilder.createEPerson(context) + .withEmail("eperson2@mail.com") + .withPassword(password) + .build(); + + 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, parentCommunity) + .withName("Sample collection") + .withSubmitterGroup(eperson) + .build(); + Collection col2 = CollectionBuilder.createCollection(context, child1) + .withName("Test collection") + .build(); + Collection col3 = CollectionBuilder.createCollection(context, child2) + .withName("Collection of sample items") + .withSubmitterGroup(eperson) + .build(); + Collection col4 = CollectionBuilder.createCollection(context, child2) + .withName("Testing autocomplete in submission") + .withSubmitterGroup(eperson2) + .build(); + context.restoreAuthSystemState(); + + String tokenEPerson = getAuthToken(eperson.getEmail(), password); + getClient(tokenEPerson).perform(get("/api/core/collections/search/findSubmitAuthorized") + .param("query", "collection")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.collections", Matchers.containsInAnyOrder( + CollectionMatcher.matchProperties(col1.getName(), col1.getID(), col1.getHandle()), + CollectionMatcher.matchProperties(col3.getName(), col3.getID(), col3.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + getClient(tokenEPerson).perform(get("/api/core/collections/search/findSubmitAuthorized") + .param("query", "COLLECTION")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.collections", Matchers.containsInAnyOrder( + CollectionMatcher.matchProperties(col1.getName(), col1.getID(), col1.getHandle()), + CollectionMatcher.matchProperties(col3.getName(), col3.getID(), col3.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + getClient(tokenEPerson).perform(get("/api/core/collections/search/findSubmitAuthorized") + .param("query", "test")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + getClient(tokenEPerson).perform(get("/api/core/collections/search/findSubmitAuthorized") + .param("query", "auto")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + String tokenEPerson2 = getAuthToken(eperson2.getEmail(), password); + getClient(tokenEPerson2).perform(get("/api/core/collections/search/findSubmitAuthorized") + .param("query", "auto")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.collections", Matchers.contains( + CollectionMatcher.matchProperties(col4.getName(), col4.getID(), col4.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + getClient(tokenEPerson2).perform(get("/api/core/collections/search/findSubmitAuthorized") + .param("query", "testing auto")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/collections/search/findSubmitAuthorized") + .param("query", "sample")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.collections", Matchers.containsInAnyOrder( + CollectionMatcher.matchProperties(col1.getName(), col1.getID(), col1.getHandle()), + CollectionMatcher.matchProperties(col3.getName(), col3.getID(), col3.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + getClient(tokenAdmin).perform(get("/api/core/collections/search/findSubmitAuthorized") + .param("query", "items sample")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.collections", Matchers.contains( + CollectionMatcher.matchProperties(col3.getName(), col3.getID(), col3.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + getClient(tokenAdmin).perform(get("/api/core/collections/search/findSubmitAuthorized") + .param("query", "test")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.collections", Matchers.containsInAnyOrder( + CollectionMatcher.matchProperties(col2.getName(), col2.getID(), col2.getHandle()), + CollectionMatcher.matchProperties(col4.getName(), col4.getID(), col4.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + } + + @Test + public void findAuthorizedByCommunityWithQueryTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .withAdminGroup(eperson).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, parentCommunity) + .withName("Sample collection") + .build(); + Collection col2 = CollectionBuilder.createCollection(context, child1) + .withName("Test collection") + .build(); + Collection col3 = CollectionBuilder.createCollection(context, child2) + .withName("Collection of sample items") + .build(); + Collection col4 = CollectionBuilder.createCollection(context, child2) + .withName("Testing autocomplete in submission") + .build(); + + context.restoreAuthSystemState(); + + String tokenAdminParentCom = getAuthToken(eperson.getEmail(), password); + getClient(tokenAdminParentCom).perform(get("/api/core/collections/search/findSubmitAuthorizedByCommunity") + .param("uuid", parentCommunity.getID().toString()) + .param("query", "sample")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.collections", Matchers.containsInAnyOrder( + CollectionMatcher.matchProperties(col1.getName(), col1.getID(), col1.getHandle()), + CollectionMatcher.matchProperties(col3.getName(), col3.getID(), col3.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(2))); + + getClient(tokenAdminParentCom).perform(get("/api/core/collections/search/findSubmitAuthorizedByCommunity") + .param("uuid", child2.getID().toString()) + .param("query", "sample")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.collections", Matchers.contains( + CollectionMatcher.matchProperties(col3.getName(), col3.getID(), col3.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + } @Test public void findAuthorizedByCommunityTest() throws Exception { @@ -497,7 +732,7 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes context.restoreAuthSystemState(); - getClient().perform(get("/api/core/collections/search/findAuthorizedByCommunity") + getClient().perform(get("/api/core/collections/search/findSubmitAuthorizedByCommunity") .param("uuid", parentCommunity.getID().toString())) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) @@ -505,15 +740,71 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(jsonPath("$._embedded").doesNotExist()); } + @Test + public void findAuthorizedByCommunityAdminsTest() throws Exception { + + context.turnOffAuthorisationSystem(); + EPerson adminParentCom = EPersonBuilder.createEPerson(context) + .withEmail("adminParentCom@mail.com") + .withPassword(password) + .build(); + + parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community") + .withAdminGroup(adminParentCom).build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity).withName("Sub Community") + .build(); + Community child2 = CommunityBuilder.createSubCommunity(context, parentCommunity).withName("Sub Community Two") + .build(); + + Collection col2 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 2").build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col3 = CollectionBuilder.createCollection(context, child2).withName("Collection 3").build(); + + context.restoreAuthSystemState(); + + String tokenAdminParentCom = getAuthToken(adminParentCom.getEmail(), password); + getClient(tokenAdminParentCom).perform(get("/api/core/collections/search/findSubmitAuthorizedByCommunity") + .param("uuid", parentCommunity.getID().toString())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.collections", Matchers.containsInAnyOrder( + CollectionMatcher.matchProperties(col1.getName(), col1.getID(), col1.getHandle()), + CollectionMatcher.matchProperties(col2.getName(), col2.getID(), col2.getHandle()), + CollectionMatcher.matchProperties(col3.getName(), col3.getID(), col3.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + + getClient(tokenAdminParentCom).perform(get("/api/core/collections/search/findSubmitAuthorizedByCommunity") + .param("uuid", child1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.collections", Matchers.contains( + CollectionMatcher.matchProperties(col1.getName(), col1.getID(), col1.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(1))); + + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(get("/api/core/collections/search/findSubmitAuthorizedByCommunity") + .param("uuid", parentCommunity.getID().toString())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.collections", Matchers.containsInAnyOrder( + CollectionMatcher.matchProperties(col1.getName(), col1.getID(), col1.getHandle()), + CollectionMatcher.matchProperties(col2.getName(), col2.getID(), col2.getHandle()), + CollectionMatcher.matchProperties(col3.getName(), col3.getID(), col3.getHandle()) + ))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + } + @Test public void findAuthorizedByCommunityWithoutUUIDTest() throws Exception { - getClient().perform(get("/api/core/collections/search/findAuthorizedByCommunity")) + getClient().perform(get("/api/core/collections/search/findSubmitAuthorizedByCommunity")) .andExpect(status().isBadRequest()); } @Test public void findAuthorizedByCommunityWithUnexistentUUIDTest() throws Exception { - getClient().perform(get("/api/core/collections/search/findAuthorizedByCommunity") + getClient().perform(get("/api/core/collections/search/findSubmitAuthorizedByCommunity") .param("uuid", UUID.randomUUID().toString())) .andExpect(status().isNotFound()); } @@ -613,7 +904,7 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes ObjectMapper mapper = new ObjectMapper(); - CollectionRest collectionRest = converter.toRest(col1, Projection.DEFAULT); + CollectionRest collectionRest = collectionConverter.convert(col1, Projection.DEFAULT); collectionRest.setMetadata(new MetadataRest() .put("dc.title", new MetadataValueRest("Electronic theses and dissertations"))); @@ -1016,7 +1307,7 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes String token = getAuthToken(eperson.getEmail(), password); ObjectMapper mapper = new ObjectMapper(); - CollectionRest collectionRest = converter.toRest(col1, Projection.DEFAULT); + CollectionRest collectionRest = collectionConverter.convert(col1, Projection.DEFAULT); collectionRest.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/CommunityAdminGroupRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityAdminGroupRestControllerIT.java index 7d5e1a393b..fb00219a4d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityAdminGroupRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/CommunityAdminGroupRestControllerIT.java @@ -10,6 +10,7 @@ package org.dspace.app.rest; import static com.jayway.jsonpath.JsonPath.read; import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata; import static org.hamcrest.Matchers.allOf; +import static org.springframework.http.MediaType.parseMediaType; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -20,21 +21,31 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; 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.builder.EPersonBuilder; +import org.dspace.app.rest.builder.GroupBuilder; +import org.dspace.app.rest.matcher.EPersonMatcher; import org.dspace.app.rest.matcher.GroupMatcher; import org.dspace.app.rest.model.GroupRest; import org.dspace.app.rest.model.MetadataRest; import org.dspace.app.rest.model.MetadataValueRest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.Collection; import org.dspace.content.Community; +import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; import org.dspace.core.Constants; +import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.service.GroupService; +import org.dspace.services.ConfigurationService; +import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; public class CommunityAdminGroupRestControllerIT extends AbstractControllerIntegrationTest { @@ -48,10 +59,19 @@ public class CommunityAdminGroupRestControllerIT extends AbstractControllerInteg @Autowired private AuthorizeService authorizeService; + @Autowired + private CollectionService collectionService; + + @Autowired + private ConfigurationService configurationService; + + Collection collection; + @Before public void setup() { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context).withName("test").build(); + collection = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); context.restoreAuthSystemState(); } @@ -437,4 +457,328 @@ public class CommunityAdminGroupRestControllerIT extends AbstractControllerInteg getClient(token).perform(delete("/api/core/communities/" + UUID.randomUUID() + "/adminGroup")) .andExpect(status().isNotFound()); } + + @Test + public void communityAdminAddMembersToCommunityAdminGroupPropertySetToFalse() throws Exception { + + context.turnOffAuthorisationSystem(); + Group adminGroup = communityService.createAdministrators(context, parentCommunity); + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + EPerson ePerson = EPersonBuilder.createEPerson(context).withEmail("testToAdd@test.com").build(); + configurationService.setProperty("core.authorization.community-admin.admin-group", false); + context.restoreAuthSystemState(); + + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform( + post("/api/eperson/groups/" + adminGroup.getID() + "/epersons") + .contentType(parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("https://localhost:8080/server/api/eperson/epersons/" + ePerson.getID())) + .andExpect(status().isForbidden()); + + token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/epersons")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.epersons", Matchers.not(Matchers.hasItem( + EPersonMatcher.matchEPersonOnEmail(ePerson.getEmail()) + )))); + } + + @Test + public void communityAdminRemoveMembersFromCommunityAdminGroupPropertySetToFalse() throws Exception { + + context.turnOffAuthorisationSystem(); + Group adminGroup = communityService.createAdministrators(context, parentCommunity); + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + EPerson ePerson = EPersonBuilder.createEPerson(context).withEmail("testToAdd@test.com").build(); + context.restoreAuthSystemState(); + + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform( + post("/api/eperson/groups/" + adminGroup.getID() + "/epersons") + .contentType(parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content("https://localhost:8080/server/api/eperson/epersons/" + ePerson.getID())); + + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/epersons")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.epersons", Matchers.hasItem( + EPersonMatcher.matchEPersonOnEmail(ePerson.getEmail()) + ))); + + configurationService.setProperty("core.authorization.community-admin.admin-group", false); + + getClient(token).perform(delete("/api/eperson/groups/" + adminGroup.getID() + "/epersons/" + ePerson.getID())) + .andExpect(status().isForbidden()); + + token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/epersons")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.epersons", Matchers.hasItem( + EPersonMatcher.matchEPersonOnEmail(ePerson.getEmail()) + ))); + } + + @Test + public void communityAdminAddChildGroupToCommunityAdminGroupPropertySetToFalse() throws Exception { + + context.turnOffAuthorisationSystem(); + Group adminGroup = communityService.createAdministrators(context, parentCommunity); + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + Group group = GroupBuilder.createGroup(context).withName("testGroup").build(); + configurationService.setProperty("core.authorization.community-admin.admin-group", false); + context.restoreAuthSystemState(); + + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform( + post("/api/eperson/groups/" + adminGroup.getID() + "/subgroups") + .contentType(parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content("https://localhost:8080/server/api/eperson/groups/" + group.getID())) + .andExpect(status().isForbidden()); + + token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/subgroups")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subgroups", Matchers.not(Matchers.hasItem( + GroupMatcher.matchGroupWithName(group.getName()) + )))); + } + + @Test + public void communityAdminRemoveChildGroupFromCommunityAdminGroupPropertySetToFalse() throws Exception { + + context.turnOffAuthorisationSystem(); + Group adminGroup = communityService.createAdministrators(context, parentCommunity); + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + Group group = GroupBuilder.createGroup(context).withName("testGroup").build(); + context.restoreAuthSystemState(); + + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform( + post("/api/eperson/groups/" + adminGroup.getID() + "/subgroups") + .contentType(parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content("https://localhost:8080/server/api/eperson/groups/" + group.getID())); + + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/subgroups")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subgroups", Matchers.hasItem( + GroupMatcher.matchGroupWithName(group.getName()) + ))); + + configurationService.setProperty("core.authorization.community-admin.admin-group", false); + + getClient(token).perform(delete("/api/eperson/groups/" + adminGroup.getID() + "/subgroups/" + group.getID())) + .andExpect(status().isForbidden()); + + token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/subgroups")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subgroups", Matchers.hasItem( + GroupMatcher.matchGroupWithName(group.getName()) + ))); + } + + @Test + public void communityAdminAddChildGroupToCollectionAdminGroupSuccess() throws Exception { + + context.turnOffAuthorisationSystem(); + Group adminGroup = collectionService.createAdministrators(context, collection); + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + Group group = GroupBuilder.createGroup(context).withName("testGroup").build(); + context.restoreAuthSystemState(); + + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform( + post("/api/eperson/groups/" + adminGroup.getID() + "/subgroups") + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content("https://localhost:8080/server/api/eperson/groups/" + group.getID())); + + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/subgroups")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subgroups", Matchers.hasItem( + GroupMatcher.matchGroupWithName(group.getName()) + ))); + + } + + @Test + public void communityAdminRemoveChildGroupFromCollectionAdminGroupSuccess() throws Exception { + + context.turnOffAuthorisationSystem(); + Group adminGroup = collectionService.createAdministrators(context, collection); + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + Group group = GroupBuilder.createGroup(context).withName("testGroup").build(); + context.restoreAuthSystemState(); + + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform( + post("/api/eperson/groups/" + adminGroup.getID() + "/subgroups") + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content("https://localhost:8080/server/api/eperson/groups/" + group.getID())); + + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/subgroups")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subgroups", Matchers.hasItem( + GroupMatcher.matchGroupWithName(group.getName()) + ))); + + + getClient(token).perform(delete("/api/eperson/groups/" + adminGroup.getID() + "/subgroups/" + group.getID())) + .andExpect(status().isNoContent()); + + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/subgroups")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subgroups", Matchers.not(Matchers.hasItem( + GroupMatcher.matchGroupWithName(group.getName()) + )))); + + } + + @Test + public void communityAdminAddMembersToCollectionAdminGroupPropertySetToFalse() throws Exception { + + context.turnOffAuthorisationSystem(); + Group adminGroup = collectionService.createAdministrators(context, collection); + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + EPerson ePerson = EPersonBuilder.createEPerson(context).withEmail("testToAdd@test.com").build(); + configurationService.setProperty("core.authorization.community-admin.collection.admin-group", false); + configurationService.setProperty("core.authorization.collection-admin.admin-group", false); + context.restoreAuthSystemState(); + + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform( + post("/api/eperson/groups/" + adminGroup.getID() + "/epersons") + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("https://localhost:8080/server/api/eperson/epersons/" + ePerson.getID())) + .andExpect(status().isForbidden()); + + token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/epersons")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.epersons", Matchers.not(Matchers.hasItem( + EPersonMatcher.matchEPersonOnEmail(ePerson.getEmail()) + )))); + } + + @Test + public void communityAdminRemoveMembersFromCollectionAdminGroupPropertySetToFalse() throws Exception { + + context.turnOffAuthorisationSystem(); + Group adminGroup = collectionService.createAdministrators(context, collection); + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + EPerson ePerson = EPersonBuilder.createEPerson(context).withEmail("testToAdd@test.com").build(); + context.restoreAuthSystemState(); + + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform( + post("/api/eperson/groups/" + adminGroup.getID() + "/epersons") + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content("https://localhost:8080/server/api/eperson/epersons/" + ePerson.getID())); + + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/epersons")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.epersons", Matchers.hasItem( + EPersonMatcher.matchEPersonOnEmail(ePerson.getEmail()) + ))); + + configurationService.setProperty("core.authorization.community-admin.collection.admin-group", false); + configurationService.setProperty("core.authorization.collection-admin.admin-group", false); + + getClient(token).perform(delete("/api/eperson/groups/" + adminGroup.getID() + "/epersons/" + ePerson.getID())) + .andExpect(status().isForbidden()); + + token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/epersons")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.epersons", Matchers.hasItem( + EPersonMatcher.matchEPersonOnEmail(ePerson.getEmail()) + ))); + } + + @Test + public void communityAdminAddChildGroupToCollectionAdminGroupPropertySetToFalse() throws Exception { + + context.turnOffAuthorisationSystem(); + Group adminGroup = collectionService.createAdministrators(context, collection); + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + Group group = GroupBuilder.createGroup(context).withName("testGroup").build(); + configurationService.setProperty("core.authorization.community-admin.collection.admin-group", false); + configurationService.setProperty("core.authorization.collection-admin.admin-group", false); + context.restoreAuthSystemState(); + + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform( + post("/api/eperson/groups/" + adminGroup.getID() + "/subgroups") + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content("https://localhost:8080/server/api/eperson/groups/" + group.getID())) + .andExpect(status().isForbidden()); + + token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/subgroups")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subgroups", Matchers.not(Matchers.hasItem( + GroupMatcher.matchGroupWithName(group.getName()) + )))); + } + + @Test + public void communityAdminRemoveChildGroupFromCollectionAdminGroupPropertySetToFalse() throws Exception { + + context.turnOffAuthorisationSystem(); + Group adminGroup = collectionService.createAdministrators(context, collection); + authorizeService.addPolicy(context, parentCommunity, Constants.ADMIN, eperson); + Group group = GroupBuilder.createGroup(context).withName("testGroup").build(); + context.restoreAuthSystemState(); + + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform( + post("/api/eperson/groups/" + adminGroup.getID() + "/subgroups") + .contentType(MediaType.parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content("https://localhost:8080/server/api/eperson/groups/" + group.getID())); + + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/subgroups")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subgroups", Matchers.hasItem( + GroupMatcher.matchGroupWithName(group.getName()) + ))); + + configurationService.setProperty("core.authorization.community-admin.collection.admin-group", false); + configurationService.setProperty("core.authorization.collection-admin.admin-group", false); + + getClient(token).perform(delete("/api/eperson/groups/" + adminGroup.getID() + "/subgroups/" + group.getID())) + .andExpect(status().isForbidden()); + + token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/subgroups")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subgroups", Matchers.hasItem( + GroupMatcher.matchGroupWithName(group.getName()) + ))); + } } 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 832778a75e..56ab3c1972 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 @@ -33,7 +33,7 @@ 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.builder.EPersonBuilder; -import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.converter.CommunityConverter; import org.dspace.app.rest.matcher.CollectionMatcher; import org.dspace.app.rest.matcher.CommunityMatcher; import org.dspace.app.rest.matcher.HalMatcher; @@ -66,7 +66,7 @@ import org.springframework.test.web.servlet.MvcResult; public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired - ConverterService converter; + CommunityConverter communityConverter; @Autowired CommunityService communityService; @@ -1360,7 +1360,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest ObjectMapper mapper = new ObjectMapper(); - CommunityRest communityRest = converter.toRest(parentCommunity, Projection.DEFAULT); + CommunityRest communityRest = communityConverter.convert(parentCommunity, Projection.DEFAULT); communityRest.setMetadata(new MetadataRest() .put("dc.title", new MetadataValueRest("Electronic theses and dissertations"))); @@ -1584,7 +1584,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest ObjectMapper mapper = new ObjectMapper(); - CommunityRest communityRest = converter.toRest(parentCommunity, Projection.DEFAULT); + CommunityRest communityRest = communityConverter.convert(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/ConfigurationRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ConfigurationRestRepositoryIT.java new file mode 100644 index 0000000000..1eab1ef68e --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ConfigurationRestRepositoryIT.java @@ -0,0 +1,55 @@ +/** + * 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.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.junit.Test; + +/** + * Integration Tests against the /api/config/properties/[property] endpoint + */ +public class ConfigurationRestRepositoryIT extends AbstractControllerIntegrationTest { + @Test + public void getSingleValue() throws Exception { + getClient().perform(get("/api/config/properties/configuration.exposed.single.value")) + .andExpect(jsonPath("$.values[0]", is("public_value"))) + .andExpect(jsonPath("$.type", is("property"))) + .andExpect(jsonPath("$.name", is("configuration.exposed.single.value"))) + .andExpect(jsonPath("$._links.self.href", is("http://localhost/api/config/properties/configuration.exposed.single.value"))); + } + + @Test + public void getArrayValue() throws Exception { + getClient().perform(get("/api/config/properties/configuration.exposed.array.value")) + .andExpect(jsonPath("$.values[0]", is("public_value_1"))) + .andExpect(jsonPath("$.values[1]", is("public_value_2"))); + } + + @Test + public void getNonExistingValue() throws Exception { + getClient().perform(get("/api/config/properties/configuration.not.existing")) + .andExpect(status().isNotFound()); + } + + @Test + public void getNonExposedValue() throws Exception { + getClient().perform(get("/api/config/properties/configuration.not.exposed")) + .andExpect(status().isNotFound()); + } + + @Test + public void getAll() throws Exception { + getClient().perform(get("/api/config/properties/")) + .andExpect(status().isMethodNotAllowed()); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index 75bffd3828..bdadb8c8d2 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -25,6 +25,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; @@ -48,15 +49,20 @@ import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.content.Collection; +import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; +import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { + @Autowired + private ConfigurationService configurationService; @Test public void createTest() throws Exception { @@ -1786,4 +1792,179 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { ); } + + @Test + public void findByMetadataByCommAdminAndByColAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson adminChild1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Oliver", "Rossi") + .withEmail("adminChild1@example.com") + .withPassword(password) + .build(); + EPerson adminCol1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("James", "Rossi") + .withEmail("adminCol1@example.com") + .withPassword(password) + .build(); + EPerson colSubmitter = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Carl", "Rossi") + .withEmail("colSubmitter@example.com") + .withPassword(password) + .build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .withAdminGroup(eperson) + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .withAdminGroup(adminChild1) + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withAdminGroup(adminCol1) + .withSubmitterGroup(colSubmitter) + .build(); + + context.restoreAuthSystemState(); + + String tokenAdminComm = getAuthToken(adminChild1.getEmail(), password); + String tokenAdminCol = getAuthToken(adminCol1.getEmail(), password); + String tokencolSubmitter = getAuthToken(colSubmitter.getEmail(), password); + + getClient(tokenAdminComm).perform(get("/api/eperson/epersons/search/byMetadata") + .param("query", "Rossi")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.epersons", Matchers.containsInAnyOrder( + EPersonMatcher.matchEPersonEntry(adminChild1), + EPersonMatcher.matchEPersonEntry(adminCol1), + EPersonMatcher.matchEPersonEntry(colSubmitter) + ))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + + getClient(tokenAdminCol).perform(get("/api/eperson/epersons/search/byMetadata") + .param("query", "Rossi")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.epersons", Matchers.containsInAnyOrder( + EPersonMatcher.matchEPersonEntry(adminChild1), + EPersonMatcher.matchEPersonEntry(adminCol1), + EPersonMatcher.matchEPersonEntry(colSubmitter) + ))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + + getClient(tokencolSubmitter).perform(get("/api/eperson/epersons/search/byMetadata") + .param("query", "Rossi")) + .andExpect(status().isForbidden()); + } + + @Test + public void findByMetadataByCommAdminAndByColAdminWithoutAuthorizationsTest() throws Exception { + context.turnOffAuthorisationSystem(); + + List confPropsCollectionAdmins = new LinkedList<>(); + confPropsCollectionAdmins.add("core.authorization.collection-admin.policies"); + confPropsCollectionAdmins.add("core.authorization.collection-admin.workflows"); + confPropsCollectionAdmins.add("core.authorization.collection-admin.submitters"); + confPropsCollectionAdmins.add("core.authorization.collection-admin.admin-group"); + + List confPropsCommunityAdmins = new LinkedList<>(); + confPropsCommunityAdmins.add("core.authorization.community-admin.policies"); + confPropsCommunityAdmins.add("core.authorization.community-admin.admin-group"); + confPropsCommunityAdmins.add("core.authorization.community-admin.collection.policies"); + confPropsCommunityAdmins.add("core.authorization.community-admin.collection.workflows"); + confPropsCommunityAdmins.add("core.authorization.community-admin.collection.submitters"); + confPropsCommunityAdmins.add("core.authorization.community-admin.collection.admin-group"); + + EPerson adminChild1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Oliver", "Rossi") + .withEmail("adminChild1@example.com") + .withPassword(password) + .build(); + EPerson adminCol = EPersonBuilder.createEPerson(context) + .withNameInMetadata("James", "Rossi") + .withEmail("adminCol1@example.com") + .withPassword(password) + .build(); + EPerson col1Submitter = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Carl", "Rossi") + .withEmail("col1Submitter@example.com") + .withPassword(password) + .build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .withAdminGroup(eperson) + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .withAdminGroup(adminChild1) + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withAdminGroup(adminCol) + .withSubmitterGroup(col1Submitter) + .build(); + + context.restoreAuthSystemState(); + + String tokenAdminCol = getAuthToken(adminCol.getEmail(), password); + String tokenAdminComm = getAuthToken(adminChild1.getEmail(), password); + + for (String prop : confPropsCollectionAdmins) { + getClient(tokenAdminCol).perform(get("/api/eperson/epersons/search/byMetadata") + .param("query", "Rossi")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.epersons", Matchers.containsInAnyOrder( + EPersonMatcher.matchEPersonEntry(adminChild1), + EPersonMatcher.matchEPersonEntry(adminCol), + EPersonMatcher.matchEPersonEntry(col1Submitter) + ))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + + configurationService.setProperty(prop, false); + } + + getClient(tokenAdminCol).perform(get("/api/eperson/epersons/search/byMetadata") + .param("query", "Rossi")) + .andExpect(status().isForbidden()); + + for (String prop : confPropsCommunityAdmins) { + getClient(tokenAdminComm).perform(get("/api/eperson/epersons/search/byMetadata") + .param("query", "Rossi")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.epersons", Matchers.containsInAnyOrder( + EPersonMatcher.matchEPersonEntry(adminChild1), + EPersonMatcher.matchEPersonEntry(adminCol), + EPersonMatcher.matchEPersonEntry(col1Submitter) + ))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + + configurationService.setProperty(prop, false); + } + + getClient(tokenAdminComm).perform(get("/api/eperson/epersons/search/byMetadata") + .param("query", "Rossi")) + .andExpect(status().isForbidden()); + } + + @Test + public void discoverableNestedLinkTest() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._links",Matchers.allOf( + hasJsonPath("$.epersons.href", + is("http://localhost/api/eperson/epersons")), + hasJsonPath("$.eperson-registration.href", + is("http://localhost/api/eperson/registrations")) + ))); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EmptyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EmptyRestRepositoryIT.java index 653ff072a0..af48a74cd3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/EmptyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/EmptyRestRepositoryIT.java @@ -38,7 +38,6 @@ public class EmptyRestRepositoryIT extends AbstractControllerIntegrationTest { //Test retrieval of all bitstreams while none exist getClient(token).perform(get("/api/core/bitstreams")) - . andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", is(0))); + . andExpect(status().isMethodNotAllowed()); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java index a1b2f9cf14..868b5d271e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java @@ -24,6 +24,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; @@ -34,6 +35,7 @@ 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.GroupBuilder; +import org.dspace.app.rest.matcher.EPersonMatcher; import org.dspace.app.rest.matcher.GroupMatcher; import org.dspace.app.rest.matcher.HalMatcher; import org.dspace.app.rest.model.GroupRest; @@ -43,10 +45,12 @@ import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; +import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; import org.dspace.core.Constants; import org.dspace.eperson.EPerson; @@ -54,7 +58,9 @@ import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.EPersonService; import org.dspace.eperson.service.GroupService; +import org.dspace.services.ConfigurationService; import org.hamcrest.Matchers; +import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -66,6 +72,24 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired ResourcePolicyService resourcePolicyService; + @Autowired + private ConfigurationService configurationService; + @Autowired + private CollectionService collectionService; + + @Autowired + private AuthorizeService authorizeService; + + Collection collection; + + @Before + public void setup() { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("test").build(); + collection = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + + context.restoreAuthSystemState(); + } @Test public void createTest() @@ -1914,5 +1938,967 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isOk()); } + @Test + public void findByMetadataByCommAdminAndByColAdminTest() throws Exception { + context.turnOffAuthorisationSystem(); + + EPerson adminChild1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Oliver", "Rossi") + .withEmail("adminChild1@example.com") + .withPassword(password) + .build(); + EPerson adminCol1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("James", "Rossi") + .withEmail("adminCol1@example.com") + .withPassword(password) + .build(); + EPerson colSubmitter = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Carl", "Rossi") + .withEmail("colSubmitter@example.com") + .withPassword(password) + .build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .withAdminGroup(adminChild1) + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withAdminGroup(adminCol1) + .withSubmitterGroup(colSubmitter) + .build(); + + Group group1 = GroupBuilder.createGroup(context) + .withName("Test group") + .build(); + + Group group2 = GroupBuilder.createGroup(context) + .withName("Test group 2") + .build(); + + Group group3 = GroupBuilder.createGroup(context) + .withName("Test group 3") + .build(); + + Group group4 = GroupBuilder.createGroup(context) + .withName("Test other group") + .build(); + + context.restoreAuthSystemState(); + + String tokenAdminComm = getAuthToken(adminChild1.getEmail(), password); + String tokenAdminCol = getAuthToken(adminCol1.getEmail(), password); + String tokenSubmitterCol = getAuthToken(colSubmitter.getEmail(), password); + + getClient(tokenAdminComm).perform(get("/api/eperson/groups/search/byMetadata") + .param("query", group1.getName())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.groups",Matchers.containsInAnyOrder( + GroupMatcher.matchGroupEntry(group1.getID(), group1.getName()), + GroupMatcher.matchGroupEntry(group2.getID(), group2.getName()), + GroupMatcher.matchGroupEntry(group3.getID(), group3.getName())))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + + getClient(tokenAdminCol).perform(get("/api/eperson/groups/search/byMetadata") + .param("query", group1.getName())) + .andExpect(status().isOk()).andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.groups", Matchers.containsInAnyOrder( + GroupMatcher.matchGroupEntry(group1.getID(), group1.getName()), + GroupMatcher.matchGroupEntry(group2.getID(), group2.getName()), + GroupMatcher.matchGroupEntry(group3.getID(), group3.getName())))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + + getClient(tokenSubmitterCol).perform(get("/api/eperson/groups/search/byMetadata") + .param("query", group1.getName())) + .andExpect(status().isForbidden()); + } + + @Test + public void findByMetadataByCommAdminAndByColAdminWithoutAuthorizationsTest() throws Exception { + context.turnOffAuthorisationSystem(); + + List confPropsCollectionAdmins = new LinkedList<>(); + confPropsCollectionAdmins.add("core.authorization.collection-admin.policies"); + confPropsCollectionAdmins.add("core.authorization.collection-admin.submitters"); + confPropsCollectionAdmins.add("core.authorization.collection-admin.workflows"); + confPropsCollectionAdmins.add("core.authorization.collection-admin.admin-group"); + + List confPropsCommunityAdmins = new LinkedList<>(); + confPropsCommunityAdmins.add("core.authorization.community-admin.policies"); + confPropsCommunityAdmins.add("core.authorization.community-admin.admin-group"); + confPropsCommunityAdmins.add("core.authorization.community-admin.collection.policies"); + confPropsCommunityAdmins.add("core.authorization.community-admin.collection.workflows"); + confPropsCommunityAdmins.add("core.authorization.community-admin.collection.submitters"); + confPropsCommunityAdmins.add("core.authorization.community-admin.collection.admin-group"); + + EPerson adminChild1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Oliver", "Rossi") + .withEmail("adminChild1@example.com") + .withPassword(password) + .build(); + EPerson adminCol1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("James", "Rossi") + .withEmail("adminCol1@example.com") + .withPassword(password) + .build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .withAdminGroup(adminChild1) + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withAdminGroup(adminCol1) + .build(); + + Group group1 = GroupBuilder.createGroup(context) + .withName("Test group") + .build(); + + Group group2 = GroupBuilder.createGroup(context) + .withName("Test group 2") + .build(); + + Group group3 = GroupBuilder.createGroup(context) + .withName("Test group 3") + .build(); + + Group group4 = GroupBuilder.createGroup(context) + .withName("Test other group") + .build(); + + context.restoreAuthSystemState(); + + String tokenAdminCol = getAuthToken(adminCol1.getEmail(), password); + String tokenAdminComm = getAuthToken(adminChild1.getEmail(), password); + + for (String prop : confPropsCollectionAdmins) { + getClient(tokenAdminCol).perform(get("/api/eperson/groups/search/byMetadata") + .param("query", group1.getName())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.groups",Matchers.containsInAnyOrder( + GroupMatcher.matchGroupEntry(group1.getID(), group1.getName()), + GroupMatcher.matchGroupEntry(group2.getID(), group2.getName()), + GroupMatcher.matchGroupEntry(group3.getID(), group3.getName())))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + + configurationService.setProperty(prop, false); + } + + getClient(tokenAdminCol).perform(get("/api/eperson/groups/search/byMetadata") + .param("query", group1.getName())) + .andExpect(status().isForbidden()); + + for (String prop : confPropsCommunityAdmins) { + getClient(tokenAdminComm).perform(get("/api/eperson/groups/search/byMetadata") + .param("query", group1.getName())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.groups",Matchers.containsInAnyOrder( + GroupMatcher.matchGroupEntry(group1.getID(), group1.getName()), + GroupMatcher.matchGroupEntry(group2.getID(), group2.getName()), + GroupMatcher.matchGroupEntry(group3.getID(), group3.getName())))) + .andExpect(jsonPath("$.page.totalElements", is(3))); + + configurationService.setProperty(prop, false); + } + + getClient(tokenAdminCol).perform(get("/api/eperson/groups/search/byMetadata") + .param("query", group1.getName())) + .andExpect(status().isForbidden()); + } + + @Test + public void commAdminManageOwnerAdminGroupTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + + context.turnOffAuthorisationSystem(); + + EPerson adminChild1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Oliver", "Rossi") + .withEmail("adminChild1@example.com") + .withPassword(password) + .build(); + + EPerson submitter1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Carl", "Rossi") + .withEmail("submitter1@example.com") + .withPassword(password) + .build(); + + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .withAdminGroup(adminChild1) + .build(); + + Group groupAdmins = child1.getAdministrators(); + + context.restoreAuthSystemState(); + + String tokenCommAdmin = getAuthToken(adminChild1.getEmail(), password); + + assertFalse(groupService.isMember(context, submitter1, groupAdmins)); + + getClient(tokenCommAdmin).perform(post("/api/eperson/groups/" + groupAdmins.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + submitter1.getID() + )) + .andExpect(status().isNoContent()); + + assertTrue(groupService.isMember(context, submitter1, groupAdmins)); + + getClient(tokenCommAdmin).perform(delete("/api/eperson/groups/" + + groupAdmins.getID() + "/epersons/" + submitter1.getID())) + .andExpect(status().isNoContent()); + + assertFalse(groupService.isMember(context, submitter1, groupAdmins)); + } + + @Test + public void colAdminManageSubmitterGroupAndAdminGroupTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + + context.turnOffAuthorisationSystem(); + + EPerson adminChild1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Oliver", "Rossi") + .withEmail("adminChild1@example.com") + .withPassword(password) + .build(); + EPerson adminCol1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("James", "Rossi") + .withEmail("adminCol1@example.com") + .withPassword(password) + .build(); + EPerson submitter1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Carl", "Rossi") + .withEmail("submitter1@example.com") + .withPassword(password) + .build(); + EPerson submitter2 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Robert", "Clarks") + .withEmail("submitter2@example.com") + .withPassword(password) + .build(); + EPerson submitter3 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Jack", "Brown") + .withEmail("submitter3@example.com") + .withPassword(password) + .build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .withAdminGroup(adminChild1) + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withAdminGroup(adminCol1) + .withSubmitterGroup(eperson) + .build(); + + Group groupSubmitters = col1.getSubmitters(); + Group groupAdmins = col1.getAdministrators(); + + context.restoreAuthSystemState(); + + String tokenAdminCol = getAuthToken(adminCol1.getEmail(), password); + + assertFalse(groupService.isMember(context, submitter1, groupSubmitters)); + assertFalse(groupService.isMember(context, submitter2, groupSubmitters)); + + getClient(tokenAdminCol).perform(post("/api/eperson/groups/" + groupSubmitters.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + submitter1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + submitter2.getID() + )) + .andExpect(status().isNoContent()); + + assertTrue(groupService.isMember(context, submitter1, groupSubmitters)); + assertTrue(groupService.isMember(context, submitter2, groupSubmitters)); + + assertFalse(groupService.isMember(context, submitter3, groupAdmins)); + + getClient(tokenAdminCol).perform( + post("/api/eperson/groups/" + groupAdmins.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + submitter3.getID() + )) + .andExpect(status().isNoContent()); + + assertTrue(groupService.isMember(context, submitter3, groupAdmins)); + } + + @Test + public void colAdminWithoutRightsTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + + context.turnOffAuthorisationSystem(); + + List confPropsCollectionAdmins = new LinkedList<>(); + confPropsCollectionAdmins.add("core.authorization.collection-admin.policies"); + confPropsCollectionAdmins.add("core.authorization.collection-admin.submitters"); + confPropsCollectionAdmins.add("core.authorization.collection-admin.workflows"); + confPropsCollectionAdmins.add("core.authorization.collection-admin.admin-group"); + + EPerson adminChild1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Oliver", "Rossi") + .withEmail("adminChild1@example.com") + .withPassword(password) + .build(); + EPerson adminCol1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("James", "Rossi") + .withEmail("adminCol1@example.com") + .withPassword(password) + .build(); + EPerson submitter1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Carl", "Rossi") + .withEmail("submitter1@example.com") + .withPassword(password) + .build(); + EPerson submitter2 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Robert", "Clarks") + .withEmail("submitter2@example.com") + .withPassword(password) + .build(); + EPerson submitter3 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Jack", "Brown") + .withEmail("submitter3@example.com") + .withPassword(password) + .build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .withAdminGroup(adminChild1) + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withAdminGroup(adminCol1) + .withSubmitterGroup(submitter2) + .build(); + + Group groupSubmitters = col1.getSubmitters(); + + context.restoreAuthSystemState(); + + String tokenAdminCol = getAuthToken(adminCol1.getEmail(), password); + + assertFalse(groupService.isMember(context, submitter1, groupSubmitters)); + + getClient(tokenAdminCol).perform( + post("/api/eperson/groups/" + groupSubmitters.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + submitter1.getID() + )) + .andExpect(status().isNoContent()); + + assertTrue(groupService.isMember(context, submitter1, groupSubmitters)); + + for (String prop : confPropsCollectionAdmins) { + configurationService.setProperty(prop, false); + } + + assertFalse(groupService.isMember(context, submitter3, groupSubmitters)); + + getClient(tokenAdminCol).perform(post("/api/eperson/groups/" + groupSubmitters.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + submitter3.getID() + )) + .andExpect(status().isForbidden()); + + assertFalse(groupService.isMember(context, submitter3, groupSubmitters)); + } + + @Test + public void communityAdminCanManageCollectionSubmittersGroupAndAdminsGroupsTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + + context.turnOffAuthorisationSystem(); + + EPerson adminChild1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Oliver", "Rossi") + .withEmail("adminChild1@example.com") + .withPassword(password) + .build(); + EPerson adminCol1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("James", "Rossi") + .withEmail("adminCol1@example.com") + .withPassword(password) + .build(); + EPerson submitter1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Carl", "Rossi") + .withEmail("submitter1@example.com") + .withPassword(password) + .build(); + EPerson submitter2 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Robert", "Clarks") + .withEmail("submitter2@example.com") + .withPassword(password) + .build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .withAdminGroup(adminChild1) + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withAdminGroup(adminCol1) + .withSubmitterGroup(eperson) + .build(); + + Group groupSubmitters = col1.getSubmitters(); + Group groupAdministrators = col1.getAdministrators(); + + context.restoreAuthSystemState(); + + String tokenAdminComm = getAuthToken(adminChild1.getEmail(), password); + + assertFalse(groupService.isMember(context, submitter1, groupSubmitters)); + assertFalse(groupService.isMember(context, submitter2, groupSubmitters)); + + getClient(tokenAdminComm).perform(post("/api/eperson/groups/" + groupSubmitters.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + submitter1.getID() + "/\n" + + REST_SERVER_URL + "eperson/groups/" + submitter2.getID() + )) + .andExpect(status().isNoContent()); + + assertTrue(groupService.isMember(context, submitter1, groupSubmitters)); + assertTrue(groupService.isMember(context, submitter2, groupSubmitters)); + + getClient(tokenAdminComm).perform(delete("/api/eperson/groups/" + + groupSubmitters.getID() + "/epersons/" + submitter1.getID())) + .andExpect(status().isNoContent()); + + assertFalse(groupService.isMember(context, submitter1, groupSubmitters)); + assertTrue(groupService.isMember(context, submitter2, groupSubmitters)); + + getClient(tokenAdminComm).perform(post("/api/eperson/groups/" + groupAdministrators.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + submitter1.getID() + )) + .andExpect(status().isNoContent()); + + assertTrue(groupService.isMember(context, submitter1, groupAdministrators)); + assertTrue(groupService.isMember(context, adminCol1, groupAdministrators)); + getClient(tokenAdminComm).perform(delete("/api/eperson/groups/" + + groupAdministrators.getID() + "/epersons/" + adminCol1.getID())) + .andExpect(status().isNoContent()); + + assertFalse(groupService.isMember(context, adminCol1, groupAdministrators)); + + } + + + @Test + public void commAdminAndColAdminCanManageItemReadGroupTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + + context.turnOffAuthorisationSystem(); + + EPerson adminChild1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Oliver", "Rossi") + .withEmail("adminChild1@example.com") + .withPassword(password) + .build(); + EPerson adminCol1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("James", "Rossi") + .withEmail("adminCol1@example.com") + .withPassword(password) + .build(); + EPerson submitter1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Carl", "Rossi") + .withEmail("submitter1@example.com") + .withPassword(password) + .build(); + EPerson submitter2 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Robert", "Clarks") + .withEmail("submitter2@example.com") + .withPassword(password) + .build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .withAdminGroup(adminChild1) + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withAdminGroup(adminCol1) + .withSubmitterGroup(eperson) + .build(); + + String itemGroupString = "ITEM"; + int defaultItemRead = Constants.DEFAULT_ITEM_READ; + Group itemReadGroup = collectionService.createDefaultReadGroup(context, col1, itemGroupString, defaultItemRead); + + context.restoreAuthSystemState(); + + String tokenAdminComm = getAuthToken(adminChild1.getEmail(), password); + String tokenAdminCol = getAuthToken(adminChild1.getEmail(), password); + + assertFalse(groupService.isMember(context, submitter1, itemReadGroup)); + assertFalse(groupService.isMember(context, submitter2, itemReadGroup)); + + getClient(tokenAdminCol).perform(post("/api/eperson/groups/" + itemReadGroup.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + submitter1.getID())) + .andExpect(status().isNoContent()); + + assertTrue(groupService.isMember(context, submitter1, itemReadGroup)); + + + getClient(tokenAdminComm).perform(post("/api/eperson/groups/" + itemReadGroup.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + submitter2.getID())) + .andExpect(status().isNoContent()); + + assertTrue(groupService.isMember(context, submitter2, itemReadGroup)); + + getClient(tokenAdminComm).perform(delete("/api/eperson/groups/" + + itemReadGroup.getID() + "/epersons/" + submitter2.getID())) + .andExpect(status().isNoContent()); + + assertFalse(groupService.isMember(context, submitter2, itemReadGroup)); + + getClient(tokenAdminCol).perform(delete("/api/eperson/groups/" + + itemReadGroup.getID() + "/epersons/" + submitter1.getID())) + .andExpect(status().isNoContent()); + + assertFalse(groupService.isMember(context, submitter1, itemReadGroup)); + + } + + @Test + public void commAdminAndColAdminCanManageBitstreamReadGroupTest() throws Exception { + + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + + context.turnOffAuthorisationSystem(); + + EPerson adminChild1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Oliver", "Rossi") + .withEmail("adminChild1@example.com") + .withPassword(password) + .build(); + EPerson adminCol1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("James", "Rossi") + .withEmail("adminCol1@example.com") + .withPassword(password) + .build(); + EPerson submitter1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Carl", "Rossi") + .withEmail("submitter1@example.com") + .withPassword(password) + .build(); + EPerson submitter2 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Robert", "Clarks") + .withEmail("submitter2@example.com") + .withPassword(password) + .build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .withAdminGroup(adminChild1) + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withAdminGroup(adminCol1) + .withSubmitterGroup(eperson) + .build(); + + String bitstreamGroupString = "BITSTREAM"; + int defaultBitstreamRead = Constants.DEFAULT_BITSTREAM_READ; + + Group bitstreamReadGroup = collectionService.createDefaultReadGroup(context, col1, bitstreamGroupString, + defaultBitstreamRead); + + context.restoreAuthSystemState(); + + String tokenAdminComm = getAuthToken(adminChild1.getEmail(), password); + String tokenAdminCol = getAuthToken(adminChild1.getEmail(), password); + + assertFalse(groupService.isMember(context, submitter1, bitstreamReadGroup)); + assertFalse(groupService.isMember(context, submitter2, bitstreamReadGroup)); + + getClient(tokenAdminCol).perform(post("/api/eperson/groups/" + bitstreamReadGroup.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + submitter1.getID())) + .andExpect(status().isNoContent()); + + assertTrue(groupService.isMember(context, submitter1, bitstreamReadGroup)); + + + getClient(tokenAdminComm).perform(post("/api/eperson/groups/" + bitstreamReadGroup.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + submitter2.getID())) + .andExpect(status().isNoContent()); + + assertTrue(groupService.isMember(context, submitter2, bitstreamReadGroup)); + + getClient(tokenAdminComm).perform(delete("/api/eperson/groups/" + + bitstreamReadGroup.getID() + "/epersons/" + submitter2.getID())) + .andExpect(status().isNoContent()); + + assertFalse(groupService.isMember(context, submitter2, bitstreamReadGroup)); + + getClient(tokenAdminCol).perform(delete("/api/eperson/groups/" + + bitstreamReadGroup.getID() + "/epersons/" + submitter1.getID())) + .andExpect(status().isNoContent()); + + assertFalse(groupService.isMember(context, submitter1, bitstreamReadGroup)); + + } + + @Test + public void commAdminAndColAdminCanManageWorkflowGroupsTest() throws Exception { + GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); + context.turnOffAuthorisationSystem(); + + EPerson adminChild1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Oliver", "Rossi") + .withEmail("adminChild1@example.com") + .withPassword(password) + .build(); + EPerson adminCol1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("James", "Rossi") + .withEmail("adminCol1@example.com") + .withPassword(password) + .build(); + EPerson submitter1 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Carl", "Rossi") + .withEmail("submitter1@example.com") + .withPassword(password) + .build(); + EPerson submitter2 = EPersonBuilder.createEPerson(context) + .withNameInMetadata("Robert", "Clarks") + .withEmail("submitter2@example.com") + .withPassword(password) + .build(); + + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .withAdminGroup(adminChild1) + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withAdminGroup(adminCol1) + .withWorkflowGroup(1, eperson) + .withWorkflowGroup(2, eperson) + .build(); + + Group workflowGroupStep1 = col1.getWorkflowStep1(context); + Group workflowGroupStep2 = col1.getWorkflowStep2(context); + + context.restoreAuthSystemState(); + + assertFalse(groupService.isMember(context, submitter1, workflowGroupStep1)); + assertFalse(groupService.isMember(context, submitter2, workflowGroupStep2)); + + String tokenAdminComm = getAuthToken(adminChild1.getEmail(), password); + String tokenAdminCol = getAuthToken(adminChild1.getEmail(), password); + + getClient(tokenAdminComm).perform(post("/api/eperson/groups/" + workflowGroupStep1.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + submitter1.getID())) + .andExpect(status().isNoContent()); + + assertTrue(groupService.isMember(context, submitter1, workflowGroupStep1)); + + getClient(tokenAdminCol).perform(post("/api/eperson/groups/" + workflowGroupStep2.getID() + "/epersons") + .contentType(parseMediaType(TEXT_URI_LIST_VALUE)) + .content(REST_SERVER_URL + "eperson/groups/" + submitter2.getID())) + .andExpect(status().isNoContent()); + + assertTrue(groupService.isMember(context, submitter2, workflowGroupStep2)); + + getClient(tokenAdminComm).perform(delete("/api/eperson/groups/" + + workflowGroupStep2.getID() + "/epersons/" + submitter2.getID())) + .andExpect(status().isNoContent()); + + getClient(tokenAdminCol).perform(delete("/api/eperson/groups/" + + workflowGroupStep1.getID() + "/epersons/" + submitter1.getID())) + .andExpect(status().isNoContent()); + + assertFalse(groupService.isMember(context, submitter1, workflowGroupStep1)); + assertFalse(groupService.isMember(context, submitter2, workflowGroupStep2)); + } + + @Test + public void collectionAdminRemoveMembersFromCollectionAdminGroupSuccess() throws Exception { + + context.turnOffAuthorisationSystem(); + Group adminGroup = collectionService.createAdministrators(context, collection); + authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); + EPerson ePerson = EPersonBuilder.createEPerson(context).withEmail("testToAdd@test.com").build(); + context.restoreAuthSystemState(); + + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform( + post("/api/eperson/groups/" + adminGroup.getID() + "/epersons") + .contentType(parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content("https://localhost:8080/server/api/eperson/epersons/" + ePerson.getID())); + + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/epersons")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.epersons", Matchers.hasItem( + EPersonMatcher.matchEPersonOnEmail(ePerson.getEmail()) + ))); + + getClient(token).perform(delete("/api/eperson/groups/" + adminGroup.getID() + "/epersons/" + ePerson.getID())) + .andExpect(status().isNoContent()); + + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/epersons")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.epersons", Matchers.not(Matchers.hasItem( + EPersonMatcher.matchEPersonOnEmail(ePerson.getEmail()) + )))); + + } + + @Test + public void collectionAdminAddChildGroupToCollectionAdminGroupSuccess() throws Exception { + + context.turnOffAuthorisationSystem(); + Group adminGroup = collectionService.createAdministrators(context, collection); + authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); + Group group = GroupBuilder.createGroup(context).withName("testGroup").build(); + context.restoreAuthSystemState(); + + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform( + post("/api/eperson/groups/" + adminGroup.getID() + "/subgroups") + .contentType(parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content("https://localhost:8080/server/api/eperson/groups/" + group.getID())); + + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/subgroups")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subgroups", Matchers.hasItem( + GroupMatcher.matchGroupWithName(group.getName()) + ))); + + } + + @Test + public void collectionAdminRemoveChildGroupFromCollectionAdminGroupSuccess() throws Exception { + + context.turnOffAuthorisationSystem(); + Group adminGroup = collectionService.createAdministrators(context, collection); + authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); + Group group = GroupBuilder.createGroup(context).withName("testGroup").build(); + context.restoreAuthSystemState(); + + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform( + post("/api/eperson/groups/" + adminGroup.getID() + "/subgroups") + .contentType(parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content("https://localhost:8080/server/api/eperson/groups/" + group.getID())); + + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/subgroups")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subgroups", Matchers.hasItem( + GroupMatcher.matchGroupWithName(group.getName()) + ))); + + + getClient(token).perform(delete("/api/eperson/groups/" + adminGroup.getID() + "/subgroups/" + group.getID())) + .andExpect(status().isNoContent()); + + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/subgroups")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subgroups", Matchers.not(Matchers.hasItem( + GroupMatcher.matchGroupWithName(group.getName()) + )))); + + } + + @Test + public void collectionAdminAddMembersToCollectionAdminGroupPropertySetToFalse() throws Exception { + + context.turnOffAuthorisationSystem(); + Group adminGroup = collectionService.createAdministrators(context, collection); + authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); + EPerson ePerson = EPersonBuilder.createEPerson(context).withEmail("testToAdd@test.com").build(); + configurationService.setProperty("core.authorization.community-admin.collection.admin-group", false); + configurationService.setProperty("core.authorization.collection-admin.admin-group", false); + context.restoreAuthSystemState(); + + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform( + post("/api/eperson/groups/" + adminGroup.getID() + "/epersons") + .contentType(parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST_VALUE)) + .content("https://localhost:8080/server/api/eperson/epersons/" + ePerson.getID())) + .andExpect(status().isForbidden()); + + token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/epersons")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.epersons", Matchers.not(Matchers.hasItem( + EPersonMatcher.matchEPersonOnEmail(ePerson.getEmail()) + )))); + + context.turnOffAuthorisationSystem(); + configurationService.setProperty("core.authorization.community-admin.collection.admin-group", true); + configurationService.setProperty("core.authorization.collection-admin.admin-group", true); + context.restoreAuthSystemState(); + + } + + @Test + public void collectionAdminRemoveMembersFromCollectionAdminGroupPropertySetToFalse() throws Exception { + + context.turnOffAuthorisationSystem(); + Group adminGroup = collectionService.createAdministrators(context, collection); + authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); + EPerson ePerson = EPersonBuilder.createEPerson(context).withEmail("testToAdd@test.com").build(); + context.restoreAuthSystemState(); + + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform( + post("/api/eperson/groups/" + adminGroup.getID() + "/epersons") + .contentType(parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content("https://localhost:8080/server/api/eperson/epersons/" + ePerson.getID())); + + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/epersons")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.epersons", Matchers.hasItem( + EPersonMatcher.matchEPersonOnEmail(ePerson.getEmail()) + ))); + + configurationService.setProperty("core.authorization.community-admin.collection.admin-group", false); + configurationService.setProperty("core.authorization.collection-admin.admin-group", false); + + getClient(token).perform(delete("/api/eperson/groups/" + adminGroup.getID() + "/epersons/" + ePerson.getID())) + .andExpect(status().isForbidden()); + + token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/epersons")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.epersons", Matchers.hasItem( + EPersonMatcher.matchEPersonOnEmail(ePerson.getEmail()) + ))); + } + + @Test + public void collectionAdminAddChildGroupToCollectionAdminGroupPropertySetToFalse() throws Exception { + + context.turnOffAuthorisationSystem(); + Group adminGroup = collectionService.createAdministrators(context, collection); + authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); + Group group = GroupBuilder.createGroup(context).withName("testGroup").build(); + configurationService.setProperty("core.authorization.community-admin.collection.admin-group", false); + configurationService.setProperty("core.authorization.collection-admin.admin-group", false); + context.restoreAuthSystemState(); + + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform( + post("/api/eperson/groups/" + adminGroup.getID() + "/subgroups") + .contentType(parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content("https://localhost:8080/server/api/eperson/groups/" + group.getID())) + .andExpect(status().isForbidden()); + + token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/subgroups")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subgroups", Matchers.not(Matchers.hasItem( + GroupMatcher.matchGroupWithName(group.getName()) + )))); + } + + @Test + public void collectionAdminRemoveChildGroupFromCollectionAdminGroupPropertySetToFalse() throws Exception { + + context.turnOffAuthorisationSystem(); + Group adminGroup = collectionService.createAdministrators(context, collection); + authorizeService.addPolicy(context, collection, Constants.ADMIN, eperson); + Group group = GroupBuilder.createGroup(context).withName("testGroup").build(); + context.restoreAuthSystemState(); + + + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform( + post("/api/eperson/groups/" + adminGroup.getID() + "/subgroups") + .contentType(parseMediaType + (org.springframework.data.rest.webmvc.RestMediaTypes + .TEXT_URI_LIST_VALUE)) + .content("https://localhost:8080/server/api/eperson/groups/" + group.getID())); + + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/subgroups")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subgroups", Matchers.hasItem( + GroupMatcher.matchGroupWithName(group.getName()) + ))); + + configurationService.setProperty("core.authorization.community-admin.collection.admin-group", false); + configurationService.setProperty("core.authorization.collection-admin.admin-group", false); + + getClient(token).perform(delete("/api/eperson/groups/" + adminGroup.getID() + "/subgroups/" + group.getID())) + .andExpect(status().isForbidden()); + + token = getAuthToken(admin.getEmail(), password); + getClient(token).perform(get("/api/eperson/groups/" + adminGroup.getID() + "/subgroups")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.subgroups", Matchers.hasItem( + GroupMatcher.matchGroupWithName(group.getName()) + ))); + } } 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 64ad7cda84..940a077f4c 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,7 +22,7 @@ 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.ConverterService; +import org.dspace.app.rest.converter.MetadataSchemaConverter; import org.dspace.app.rest.matcher.HalMatcher; import org.dspace.app.rest.matcher.MetadataschemaMatcher; import org.dspace.app.rest.model.MetadataSchemaRest; @@ -46,9 +46,9 @@ public class MetadataSchemaRestRepositoryIT extends AbstractControllerIntegratio private static final String TEST_NAME_UPDATED = "testSchemaNameUpdated"; private static final String TEST_NAMESPACE_UPDATED = "testSchemaNameSpaceUpdated"; - @Autowired - ConverterService converter; + @Autowired + private MetadataSchemaConverter metadataSchemaConverter; @Test public void findAll() throws Exception { @@ -90,7 +90,7 @@ public class MetadataSchemaRestRepositoryIT extends AbstractControllerIntegratio .build(); context.restoreAuthSystemState(); - MetadataSchemaRest metadataSchemaRest = converter.toRest(metadataSchema, Projection.DEFAULT); + MetadataSchemaRest metadataSchemaRest = metadataSchemaConverter.convert(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 index 92c37007b1..fdca31d07b 100644 --- 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 @@ -13,14 +13,21 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder 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.sql.SQLException; import java.util.LinkedList; +import java.util.List; +import java.util.Random; +import org.apache.commons.codec.CharEncoding; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.io.IOUtils; import org.dspace.app.rest.builder.ProcessBuilder; import org.dspace.app.rest.matcher.PageMatcher; +import org.dspace.app.rest.matcher.ProcessFileTypesMatcher; import org.dspace.app.rest.matcher.ProcessMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.content.Bitstream; import org.dspace.content.ProcessStatus; import org.dspace.scripts.DSpaceCommandLineParameter; import org.dspace.scripts.Process; @@ -43,6 +50,13 @@ public class ProcessRestRepositoryIT extends AbstractControllerIntegrationTest { @Before public void setup() throws SQLException { + CollectionUtils.emptyIfNull(processService.findAll(context)).stream().forEach(process -> { + try { + processService.delete(context, process); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); parameters.add(new DSpaceCommandLineParameter("-r", "test")); parameters.add(new DSpaceCommandLineParameter("-i", null)); @@ -201,15 +215,115 @@ public class ProcessRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isForbidden()); } + @Test + public void getProcessFiles() throws Exception { + Process newProcess = ProcessBuilder.createProcess(context, eperson, "mock-script", new LinkedList<>()).build(); + + try (InputStream is = IOUtils.toInputStream("Test File For Process", CharEncoding.UTF_8)) { + processService.appendFile(context, process, is, "inputfile", "test.csv"); + } + Bitstream bitstream = processService.getBitstream(context, process, "inputfile"); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(get("/api/system/processes/" + process.getID() + "/files")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.files[0].name", is("test.csv"))) + .andExpect(jsonPath("$._embedded.files[0].uuid", is(bitstream.getID().toString()))) + .andExpect(jsonPath("$._embedded.files[0].metadata['dspace.process.filetype']" + + "[0].value", is("inputfile"))); + + } + + @Test + public void getProcessFilesByFileType() throws Exception { + Process newProcess = ProcessBuilder.createProcess(context, eperson, "mock-script", new LinkedList<>()).build(); + + try (InputStream is = IOUtils.toInputStream("Test File For Process", CharEncoding.UTF_8)) { + processService.appendFile(context, process, is, "inputfile", "test.csv"); + } + Bitstream bitstream = processService.getBitstream(context, process, "inputfile"); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(get("/api/system/processes/" + process.getID() + "/files/inputfile")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.bitstreams[0].name", is("test.csv"))) + .andExpect(jsonPath("$._embedded.bitstreams[0].uuid", is(bitstream.getID().toString()))) + .andExpect(jsonPath("$._embedded.bitstreams[0].metadata['dspace.process.filetype']" + + "[0].value", is("inputfile"))); + + } + + @Test + public void getProcessFilesTypes() throws Exception { + try (InputStream is = IOUtils.toInputStream("Test File For Process", CharEncoding.UTF_8)) { + processService.appendFile(context, process, is, "inputfile", "test.csv"); + } + + List fileTypesToCheck = new LinkedList<>(); + fileTypesToCheck.add("inputfile"); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(get("/api/system/processes/" + process.getID() + "/filetypes")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", ProcessFileTypesMatcher + .matchProcessFileTypes("filetypes-" + process.getID(), fileTypesToCheck))); + + + } + + @Test + public void getProcessFilesTypesForbidden() throws Exception { + try (InputStream is = IOUtils.toInputStream("Test File For Process", CharEncoding.UTF_8)) { + processService.appendFile(context, process, is, "inputfile", "test.csv"); + } + + List fileTypesToCheck = new LinkedList<>(); + fileTypesToCheck.add("inputfile"); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token).perform(get("/api/system/processes/" + process.getID() + "/filetypes")) + .andExpect(status().isForbidden()); + + + } + + @Test + public void getProcessFilesTypesUnAuthorized() throws Exception { + try (InputStream is = IOUtils.toInputStream("Test File For Process", CharEncoding.UTF_8)) { + processService.appendFile(context, process, is, "inputfile", "test.csv"); + } + + List fileTypesToCheck = new LinkedList<>(); + fileTypesToCheck.add("inputfile"); + + getClient().perform(get("/api/system/processes/" + process.getID() + "/filetypes")) + .andExpect(status().isUnauthorized()); + + } + + @Test + public void getProcessFilesTypesRandomProcessId() throws Exception { + try (InputStream is = IOUtils.toInputStream("Test File For Process", CharEncoding.UTF_8)) { + processService.appendFile(context, process, is, "inputfile", "test.csv"); + } + + List fileTypesToCheck = new LinkedList<>(); + fileTypesToCheck.add("inputfile"); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(get("/api/system/processes/" + new Random() + "/filetypes")) + .andExpect(status().isNotFound()); + + + } + @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/ResourcePolicyRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java index 4ce86797f0..1b207fbeee 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ResourcePolicyRestRepositoryIT.java @@ -53,6 +53,7 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.factory.EPersonServiceFactory; import org.hamcrest.Matchers; +import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -503,7 +504,9 @@ public class ResourcePolicyRestRepositoryIT extends AbstractControllerIntegratio .andExpect(jsonPath("$.page.totalElements", is(2))); } + // This test is currently not working as intended, needs to be reviewed. @Test + @Ignore public void findResourcePoliciesOfOneResourceWithActionTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -2404,4 +2407,17 @@ public class ResourcePolicyRestRepositoryIT extends AbstractControllerIntegratio hasJsonPath("$.endDate", is(formatDate.format(endDate))), hasJsonPath("$.description", nullValue())))); } + + @Test + public void discoverableNestedLinkTest() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._links",Matchers.allOf( + hasJsonPath("$.resourcepolicies.href", + is("http://localhost/api/authz/resourcepolicies")), + hasJsonPath("$.resourcepolicy-search.href", + is("http://localhost/api/authz/resourcepolicy/search")) + ))); + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RootRestResourceControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RootRestResourceControllerIT.java index 89ed50ec24..6c1c4a9427 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/RootRestResourceControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/RootRestResourceControllerIT.java @@ -33,9 +33,9 @@ public class RootRestResourceControllerIT extends AbstractControllerIntegrationT .andExpect(status().isOk()) //We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$.dspaceURL", Matchers.is("http://localhost:3000"))) + .andExpect(jsonPath("$.dspaceUI", Matchers.is("http://localhost:4000"))) .andExpect(jsonPath("$.dspaceName", Matchers.is("DSpace at My University"))) - .andExpect(jsonPath("$.dspaceRest", Matchers.is(BASE_REST_SERVER_URL))) + .andExpect(jsonPath("$.dspaceServer", Matchers.is(BASE_REST_SERVER_URL))) .andExpect(jsonPath("$.type", Matchers.is("root"))); } @@ -71,4 +71,4 @@ public class RootRestResourceControllerIT extends AbstractControllerIntegrationT ; } -} \ No newline at end of file +} 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 index 930f3ab4d7..c1ba31305e 100644 --- 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 @@ -7,22 +7,30 @@ */ package org.dspace.app.rest; +import static com.jayway.jsonpath.JsonPath.read; 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.fileUpload; 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.io.IOException; import java.sql.SQLException; import java.util.Arrays; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import com.google.gson.Gson; import org.apache.commons.collections4.CollectionUtils; +import org.dspace.app.rest.builder.CollectionBuilder; +import org.dspace.app.rest.builder.CommunityBuilder; +import org.dspace.app.rest.builder.ItemBuilder; +import org.dspace.app.rest.builder.ProcessBuilder; import org.dspace.app.rest.converter.DSpaceRunnableParameterConverter; import org.dspace.app.rest.matcher.PageMatcher; import org.dspace.app.rest.matcher.ProcessMatcher; @@ -30,14 +38,21 @@ 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.authorize.AuthorizeException; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; import org.dspace.content.ProcessStatus; +import org.dspace.content.service.BitstreamService; import org.dspace.scripts.DSpaceCommandLineParameter; -import org.dspace.scripts.DSpaceRunnable; +import org.dspace.scripts.configuration.ScriptConfiguration; 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; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { @@ -45,7 +60,10 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { private ProcessService processService; @Autowired - private List dSpaceRunnableList; + private BitstreamService bitstreamService; + + @Autowired + private List scriptConfigurations; @Autowired private DSpaceRunnableParameterConverter dSpaceRunnableParameterConverter; @@ -57,10 +75,14 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { 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()) + ScriptMatcher.matchScript(scriptConfigurations.get(0).getName(), + scriptConfigurations.get(0).getDescription()), + ScriptMatcher.matchScript(scriptConfigurations.get(1).getName(), + scriptConfigurations.get(1).getDescription()), + ScriptMatcher.matchScript(scriptConfigurations.get(2).getName(), + scriptConfigurations.get(2).getDescription()), + ScriptMatcher.matchScript(scriptConfigurations.get(3).getName(), + scriptConfigurations.get(3).getDescription()) ))); } @@ -84,28 +106,28 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { 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("$._embedded.scripts", Matchers.not(Matchers.hasItem( + ScriptMatcher.matchScript(scriptConfigurations.get(0).getName(), + scriptConfigurations.get(0).getDescription()) )))) + .andExpect(jsonPath("$._embedded.scripts", hasItem( + ScriptMatcher.matchScript(scriptConfigurations.get(2).getName(), + scriptConfigurations.get(2).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()) + ScriptMatcher.matchScript(scriptConfigurations.get(1).getName(), + scriptConfigurations.get(1).getDescription()) ))) + .andExpect(jsonPath("$._embedded.scripts", Matchers.not(hasItem( + ScriptMatcher.matchScript(scriptConfigurations.get(0).getName(), + scriptConfigurations.get(0).getDescription()) + )))) .andExpect(jsonPath("$.page", is(PageMatcher.pageEntry(1, 1)))); } @@ -117,7 +139,7 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { getClient(token).perform(get("/api/system/scripts/mock-script")) .andExpect(status().isOk()) .andExpect(jsonPath("$", ScriptMatcher - .matchMockScript(dSpaceRunnableList.get(1).getOptions()))); + .matchMockScript(scriptConfigurations.get(3).getOptions()))); } @Test @@ -154,13 +176,23 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { String token = getAuthToken(admin.getEmail(), password); + AtomicReference idRef = new AtomicReference<>(); + + try { + 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)))) + .andDo(result -> idRef + .set(read(result.getResponse().getContentAsString(), "$.processId"))); + } finally { + ProcessBuilder.deleteProcess(idRef.get()); + } + - 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 @@ -182,19 +214,29 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { parameters.add(new DSpaceCommandLineParameter("-q", null)); List list = parameters.stream() - .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter - .convert(dSpaceCommandLineParameter, Projection.DEFAULT)).collect(Collectors.toList()); + .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)))); + AtomicReference idRef = new AtomicReference<>(); + + try { + 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)))) + .andDo(result -> idRef + .set(read(result.getResponse().getContentAsString(), "$.processId"))); + } finally { + ProcessBuilder.deleteProcess(idRef.get()); + } } @Test @@ -214,8 +256,9 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { parameters.add(new DSpaceCommandLineParameter("-i", null)); List list = parameters.stream() - .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter - .convert(dSpaceCommandLineParameter, Projection.DEFAULT)).collect(Collectors.toList()); + .map(dSpaceCommandLineParameter -> dSpaceRunnableParameterConverter + .convert(dSpaceCommandLineParameter, Projection.DEFAULT)) + .collect(Collectors.toList()); String token = getAuthToken(admin.getEmail(), password); List acceptableProcessStatuses = new LinkedList<>(); @@ -223,16 +266,24 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { 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)))); + AtomicReference idRef = new AtomicReference<>(); + try { + 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)))) + .andDo(result -> idRef + .set(read(result.getResponse().getContentAsString(), "$.processId"))); + } finally { + ProcessBuilder.deleteProcess(idRef.get()); + } } @Test @@ -243,12 +294,77 @@ public class ScriptRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isBadRequest()); } + @Test + public void postProcessAdminWithFileSuccess() throws Exception { + LinkedList parameters = new LinkedList<>(); + + parameters.add(new DSpaceCommandLineParameter("-r", "test")); + parameters.add(new DSpaceCommandLineParameter("-i", null)); + + + 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(); + + //2. Three public items that are readable by Anonymous with different subjects + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + String bitstreamContent = "Hello, World!"; + MockMultipartFile bitstreamFile = new MockMultipartFile("file", + "helloProcessFile.txt", MediaType.TEXT_PLAIN_VALUE, + bitstreamContent.getBytes()); + parameters.add(new DSpaceCommandLineParameter("-f", "helloProcessFile.txt")); + + 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)); + + AtomicReference idRef = new AtomicReference<>(); + + try { + getClient(token) + .perform(fileUpload("/api/system/scripts/mock-script/processes").file(bitstreamFile) + .param("properties", + new Gson().toJson(list))) + .andExpect(status().isAccepted()) + .andExpect(jsonPath("$", is( + ProcessMatcher.matchProcess("mock-script", + String.valueOf(admin.getID()), + parameters, + acceptableProcessStatuses)))) + .andDo(result -> idRef + .set(read(result.getResponse().getContentAsString(), "$.processId"))); + } finally { + ProcessBuilder.deleteProcess(idRef.get()); + } + } + + @After public void destroy() throws Exception { CollectionUtils.emptyIfNull(processService.findAll(context)).stream().forEach(process -> { try { processService.delete(context, process); - } catch (SQLException e) { + } catch (SQLException | AuthorizeException | IOException e) { throw new RuntimeException(e); } }); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ShibbolethRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ShibbolethRestControllerIT.java index a690e539ab..e13d091168 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ShibbolethRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ShibbolethRestControllerIT.java @@ -27,7 +27,7 @@ public class ShibbolethRestControllerIT extends AbstractControllerIntegrationTes getClient(token).perform(get("/api/authn/shibboleth")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost:3000")); + .andExpect(redirectedUrl("http://localhost:4000")); } @Test diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubResourcePermissionsIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubResourcePermissionsIT.java new file mode 100644 index 0000000000..d4399a8047 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubResourcePermissionsIT.java @@ -0,0 +1,442 @@ +/** + * 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.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.InputStream; + +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.ItemBuilder; +import org.dspace.app.rest.matcher.BundleMatcher; +import org.dspace.app.rest.matcher.CommunityMatcher; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +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.Item; +import org.hamcrest.Matchers; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +public class SubResourcePermissionsIT extends AbstractControllerIntegrationTest { + + + @Autowired + private AuthorizeService authorizeService; + + @Test + public void itemBundlePrivateItemPermissionTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + + //2. Three public items that are readable by Anonymous with different subjects + Item privateItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + Bitstream bitstream; + Bundle bundle; + String bitstreamContent = "Dummy content"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder.createBitstream(context, privateItem1, is) + .withName("Bitstream") + .withMimeType("text/plain") + .build(); + } + + bundle = BundleBuilder.createBundle(context, privateItem1) + .withName("testname") + .withBitstream(bitstream) + .build(); + + + authorizeService.removeAllPolicies(context, privateItem1); + + String token = getAuthToken(admin.getEmail(), password); + + // Test admin retrieval of subresource bundle of private item + // should succeed + getClient(token).perform(get("/api/core/items/" + privateItem1.getID() + "/bundles")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.bundles", Matchers.hasItem(BundleMatcher + .matchProperties( + bundle.getName(), + bundle.getID(), + bundle.getHandle(), + bundle.getType())))); + + token = getAuthToken(eperson.getEmail(), password); + + // Test eperson retrieval of subresource bundle of private item + // shouldn't succeed + getClient(token).perform(get("/api/core/items/" + privateItem1.getID() + "/bundles")) + .andExpect(status().isForbidden()); + + // Test anon retrieval of subresource bundle of private item + // shouldn't succeed + getClient().perform(get("/api/core/items/" + privateItem1.getID() + "/bundles")) + .andExpect(status().isUnauthorized()); + + token = getAuthToken(admin.getEmail(), password); + + // Test item retrieval for admin on private item + // Should succeed + getClient(token).perform(get("/api/core/items/" + privateItem1.getID()) + .param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.bundles._embedded.bundles", Matchers.hasItem(BundleMatcher + .matchProperties(bundle.getName(), bundle.getID(), + bundle.getHandle(), bundle.getType())))); + + token = getAuthToken(eperson.getEmail(), password); + + // Test item retrieval for normal eperson on private item + // Shouldn't succeed + getClient(token).perform(get("/api/core/items/" + privateItem1.getID()) + .param("projection", "full")) + .andExpect(status().isForbidden()); + + // Test item retrieval for anon on private item + // Shouldn't succeed + getClient().perform(get("/api/core/items/" + privateItem1.getID()) + .param("projection", "full")) + .andExpect(status().isUnauthorized()); + + + // Test item retrieval for normal eperson on public bundle + // Should succeed + getClient(token).perform(get("/api/core/bundles/" + bundle.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", BundleMatcher + .matchProperties(bundle.getName(), bundle.getID(), bundle.getHandle(), bundle.getType()))); + + + // Test item retrieval for anon on public bundle + // Should succeed + getClient().perform(get("/api/core/bundles/" + bundle.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", BundleMatcher + .matchProperties(bundle.getName(), bundle.getID(), bundle.getHandle(), bundle.getType()))); + + } + + @Test + public void itemBundlePrivateBundlePermissionTest() throws Exception { + + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child1).withName("Collection 2").build(); + + //2. Three public items that are readable by Anonymous with different subjects + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + Bitstream bitstream; + Bundle bundle; + String bitstreamContent = "Dummy content"; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder.createBitstream(context, publicItem1, is) + .withName("Bitstream") + .withMimeType("text/plain") + .build(); + } + + bundle = BundleBuilder.createBundle(context, publicItem1) + .withName("testname") + .withBitstream(bitstream) + .build(); + + + authorizeService.removeAllPolicies(context, bundle); + + String token = getAuthToken(admin.getEmail(), password); + + // Bundle retrieval for public item, checking private bundle as admin + // Should succeed + getClient(token).perform(get("/api/core/items/" + publicItem1.getID() + "/bundles")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.bundles", Matchers.hasItem(BundleMatcher + .matchProperties( + bundle.getName(), + bundle.getID(), + bundle.getHandle(), + bundle.getType())))); + + token = getAuthToken(eperson.getEmail(), password); + + // Bundle retrieval for public item, checking private bundle as normal eperson + // Shouldn't contain the private bundle + getClient(token).perform(get("/api/core/items/" + publicItem1.getID() + "/bundles")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.bundles", Matchers.not(Matchers.hasItem( + BundleMatcher.matchProperties(bundle.getName(), bundle.getID(), bundle.getHandle(), + bundle.getType()))))); + + // Bundle retrieval for public item, checking private bundle as anon + // Shouldn't contain the private bundle + getClient().perform(get("/api/core/items/" + publicItem1.getID() + "/bundles")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.bundles", Matchers.not(Matchers.hasItem( + BundleMatcher.matchProperties(bundle.getName(), bundle.getID(), bundle.getHandle(), + bundle.getType()))))); + + token = getAuthToken(admin.getEmail(), password); + + // Admin retrieval for public item + // Should succeed + getClient(token).perform(get("/api/core/items/" + publicItem1.getID())) + .andExpect(status().isOk()); + + token = getAuthToken(eperson.getEmail(), password); + + // Normal EPerson retrieval for public item + // Should succeed + getClient(token).perform(get("/api/core/items/" + publicItem1.getID())) + .andExpect(status().isOk()); + + // Anon retrieval for public item + // Should succeed + getClient().perform(get("/api/core/items/" + publicItem1.getID())) + .andExpect(status().isOk()); + + token = getAuthToken(admin.getEmail(), password); + + // Admin full projection retrieval for public item with private bundles + // Should succeed + getClient(token).perform(get("/api/core/items/" + publicItem1.getID()) + .param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.bundles._embedded.bundles", Matchers.hasItem( + BundleMatcher.matchProperties(bundle.getName(), bundle.getID(), + bundle.getHandle(), bundle.getType())))); + + token = getAuthToken(eperson.getEmail(), password); + + // Normal EPerson full projection retrieval for public item with private bundles + // Shouldn't succeed + getClient(token).perform(get("/api/core/items/" + publicItem1.getID()) + .param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.bundles._embedded.bundles", Matchers.not(Matchers.hasItem( + BundleMatcher.matchProperties(bundle.getName(), bundle.getID(), + bundle.getHandle(), bundle.getType()))))); + + + // Anon full projection retrieval for public item with private bundles + // Shouldn't succeed + getClient().perform(get("/api/core/items/" + publicItem1.getID()) + .param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.bundles._embedded.bundles", Matchers.not(Matchers.hasItem( + BundleMatcher.matchProperties(bundle.getName(), bundle.getID(), + bundle.getHandle(), bundle.getType()))))); + + + token = getAuthToken(admin.getEmail(), password); + + // Admin retrieval of private bundle + // Should succeed + getClient(token).perform(get("/api/core/bundles/" + bundle.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", BundleMatcher + .matchProperties(bundle.getName(), bundle.getID(), bundle.getHandle(), bundle.getType()))); + + token = getAuthToken(eperson.getEmail(), password); + + // Normal EPerson retrieval of private bundle + // Shouldn't succeed + getClient(token).perform(get("/api/core/bundles/" + bundle.getID())) + .andExpect(status().isForbidden()); + + // Anon retrieval of private bundle + // Shouldn't succeed + getClient().perform(get("/api/core/bundles/" + bundle.getID())) + .andExpect(status().isUnauthorized()); + + } + + @Test + public void parentCommunityOfPrivateCollectionPermissionTest() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + + authorizeService.removeAllPolicies(context, col1); + + String adminToken = getAuthToken(admin.getEmail(), password); + + // Calling parentCommunity of a private collection as an admin + // Should succeed + getClient(adminToken).perform(get("/api/core/collections/" + col1.getID() + "/parentCommunity")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", CommunityMatcher + .matchCommunityEntry(parentCommunity.getID(), parentCommunity.getHandle()))); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + + // Calling parentCommunity of a private collection as a normal eperson + // Shouldn't succeed + getClient(epersonToken).perform(get("/api/core/collections/" + col1.getID() + "/parentCommunity")) + .andExpect(status().isForbidden()); + + // Calling parentCommunity of a private collection as an anon user + // Shouldn't succeed + getClient().perform(get("/api/core/collections/" + col1.getID() + "/parentCommunity")) + .andExpect(status().isUnauthorized()); + + // Calling public parentCommunity as an admin user + // Should succeed + getClient(adminToken).perform(get("/api/core/community/" + parentCommunity.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", CommunityMatcher + .matchCommunityEntry(parentCommunity.getID(), parentCommunity.getHandle()))); + + // Calling public parentCommunity as a normal EPerson + // Should succeed + getClient(epersonToken).perform(get("/api/core/community/" + parentCommunity.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", CommunityMatcher + .matchCommunityEntry(parentCommunity.getID(), parentCommunity.getHandle()))); + + // Calling public parentCommunity as an anon user + // Should succeed + getClient().perform(get("/api/core/community/" + parentCommunity.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", CommunityMatcher + .matchCommunityEntry(parentCommunity.getID(), parentCommunity.getHandle()))); + + // Calling fullProjection, as an admin user, of a private Collection should contain the parentCommunity + getClient(adminToken).perform(get("/api/core/collections/" + col1.getID()) + .param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.parentCommunity", CommunityMatcher + .matchCommunityEntry(parentCommunity.getID(), parentCommunity.getHandle()))); + + // Calling full projection, as a normal eperson ,of a private collection should return 403 + getClient(epersonToken).perform(get("/api/core/collections/" + col1.getID()) + .param("projection", "full")) + .andExpect(status().isForbidden()); + + // Calling full projection, as an anon user, of a collection should return 401 + getClient().perform(get("/api/core/collections/" + col1.getID()) + .param("projection", "full")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void privateParentCommunityOfCollectionPermissionTest() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); + + authorizeService.removeAllPolicies(context, parentCommunity); + + String adminToken = getAuthToken(admin.getEmail(), password); + + // Calling private parentCommunity of a collection as an admin + // Should succeed + getClient(adminToken).perform(get("/api/core/collections/" + col1.getID() + "/parentCommunity")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", CommunityMatcher + .matchCommunityEntry(parentCommunity.getID(), parentCommunity.getHandle()))); + + String epersonToken = getAuthToken(eperson.getEmail(), password); + + // Calling private parentCommunity of a collection as a normal eperson + // Shouldn't succeed + getClient(epersonToken).perform(get("/api/core/collections/" + col1.getID() + "/parentCommunity")) + .andExpect(status().isNoContent()); + + // Calling private parentCommunity of a collection as an anon user + // Shouldn't succeed + getClient().perform(get("/api/core/collections/" + col1.getID() + "/parentCommunity")) + .andExpect(status().isNoContent()); + + // Calling private parentCommunity as an admin user + // Should succeed + getClient(adminToken).perform(get("/api/core/community/" + parentCommunity.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", CommunityMatcher + .matchCommunityEntry(parentCommunity.getID(), parentCommunity.getHandle()))); + + // Calling private parentCommunity as a normal EPerson + // Shouldn't succeed + getClient(epersonToken).perform(get("/api/core/community/" + parentCommunity.getID())) + .andExpect(status().isForbidden()); + + // Calling private parentCommunity as an anon user + // Shouldn't succeed + getClient().perform(get("/api/core/community/" + parentCommunity.getID())) + .andExpect(status().isUnauthorized()); + + // Calling fullProjection, as an admin user, of a private Collection should contain the parentCommunity + getClient(adminToken).perform(get("/api/core/collections/" + col1.getID()) + .param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.parentCommunity", CommunityMatcher + .matchCommunityEntry(parentCommunity.getID(), parentCommunity.getHandle()))); + + // Calling full projection, as a normal eperson, of a public collection shouldn't return private parentCommunity + getClient(epersonToken).perform(get("/api/core/collections/" + col1.getID()) + .param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.parentCommunity").doesNotExist()); + + // Calling full projection, as an anon user, of a collection shouldn't return private parentCommunity + getClient().perform(get("/api/core/collections/" + col1.getID()) + .param("projection", "full")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.parentCommunity").doesNotExist()); + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionCCLicenseRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionCCLicenseRestRepositoryIT.java new file mode 100644 index 0000000000..9267cef6c5 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionCCLicenseRestRepositoryIT.java @@ -0,0 +1,82 @@ +/** + * 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.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.matcher.SubmissionCCLicenseMatcher; +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.hamcrest.Matchers; +import org.junit.Test; + +/** + * Class to the methods from the SubmissionCCLicenseRestRepository + * Since the CC Licenses are obtained from the CC License API, a mock service has been implemented + * This mock service will return a fixed set of CC Licenses using a similar structure to the ones obtained from the + * CC License API. + * Refer to {@link org.dspace.license.MockCCLicenseConnectorServiceImpl} for more information + */ +public class SubmissionCCLicenseRestRepositoryIT extends AbstractControllerIntegrationTest { + + + /** + * Test the findAll method form the SubmissionCCLicenseRestRepository + * + * @throws Exception + */ + @Test + public void findAllTest() throws Exception { + String epersonToken = getAuthToken(eperson.getEmail(), password); + + getClient(epersonToken).perform(get("/api/config/submissioncclicenses")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.submissioncclicenses", Matchers.containsInAnyOrder( + SubmissionCCLicenseMatcher.matchLicenseEntry(1, new int[]{3, 2, 3}), + SubmissionCCLicenseMatcher.matchLicenseEntry(2, new int[]{2}), + SubmissionCCLicenseMatcher.matchLicenseEntry(3, new int[]{}) + ))); + } + + @Test + public void findOneTest() throws Exception { + String epersonToken = getAuthToken(eperson.getEmail(), password); + + getClient(epersonToken).perform(get("/api/config/submissioncclicenses/license1")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.is( + SubmissionCCLicenseMatcher.matchLicenseEntry(1, new int[]{3, 2, 3}) + ))); + } + + @Test + public void findOneTestNonExistingLicense() throws Exception { + String epersonToken = getAuthToken(eperson.getEmail(), password); + + getClient(epersonToken).perform(get("/api/config/submissioncclicenses/non-existing-license")) + .andExpect(status().isNotFound()); + } + + @Test + public void findAllTestUnAuthorized() throws Exception { + getClient().perform(get("/api/config/submissioncclicenses")) + .andExpect(status().isUnauthorized()); + } + + @Test + public void findOneTestUnAuthorized() throws Exception { + + getClient().perform(get("/api/config/submissioncclicenses/license1")) + .andExpect(status().isUnauthorized()); + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionCCLicenseUrlRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionCCLicenseUrlRepositoryIT.java new file mode 100644 index 0000000000..84fe06ce19 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionCCLicenseUrlRepositoryIT.java @@ -0,0 +1,99 @@ +/** + * 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.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.junit.Test; + +/** + * Class to the methods from the SubmissionCCLicenseUrlRepository + * Since the CC Licenses and the corresponding URIs are obtained from the CC License API, a mock service has been + * implemented. + * This mock service will return a fixed set of CC Licenses using a similar structure to the ones obtained from the + * CC License API. + * Refer to {@link org.dspace.license.MockCCLicenseConnectorServiceImpl} for more information + */ +public class SubmissionCCLicenseUrlRepositoryIT extends AbstractControllerIntegrationTest { + + + @Test + public void searchRightsByQuestionsTest() throws Exception { + String epersonToken = getAuthToken(eperson.getEmail(), password); + + getClient(epersonToken).perform(get( + "/api/config/submissioncclicenseUrls/search/rightsByQuestions?license=license2&answer_license2-field0" + + "=license2-field0-enum1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.url", is("mock-license-uri"))) + .andExpect(jsonPath("$.type", is("submissioncclicenseUrl"))) + .andExpect(jsonPath("$._links.self.href", + is("http://localhost/api/config/submissioncclicenseUrls/search/rightsByQuestions" + + "?license=license2" + + "&answer_license2-field0=license2-field0-enum1"))); + } + + @Test + public void searchRightsByQuestionsTestLicenseForLicenseWithoutQuestions() throws Exception { + String epersonToken = getAuthToken(eperson.getEmail(), password); + + getClient(epersonToken) + .perform(get("/api/config/submissioncclicenseUrls/search/rightsByQuestions?license=license3")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.url", is("mock-license-uri"))) + .andExpect(jsonPath("$.type", is("submissioncclicenseUrl"))) + .andExpect(jsonPath("$._links.self.href", + is("http://localhost/api/config/submissioncclicenseUrls/search/rightsByQuestions" + + "?license=license3"))); + } + + @Test + public void searchRightsByQuestionsNonExistingLicense() throws Exception { + String epersonToken = getAuthToken(eperson.getEmail(), password); + + getClient(epersonToken).perform(get( + "/api/config/submissioncclicenseUrls/search/rightsByQuestions?license=nonexisting-license" + + "&answer_license2-field0=license2-field0-enum1")) + .andExpect(status().isNotFound()); + } + + @Test + public void searchRightsByQuestionsMissingRequiredAnswer() throws Exception { + String epersonToken = getAuthToken(eperson.getEmail(), password); + + getClient(epersonToken).perform(get( + "/api/config/submissioncclicenseUrls/search/rightsByQuestions?license=license1&answer_license1field0" + + "=license1field0enum1")) + .andExpect(status().isBadRequest()); + } + + @Test + public void searchRightsByQuestionsAdditionalNonExistingAnswer() throws Exception { + String epersonToken = getAuthToken(eperson.getEmail(), password); + + getClient(epersonToken).perform(get( + "/api/config/submissioncclicenseUrls/search/rightsByQuestions?license=license2" + + "&answer_license2field0=license2field0enum1&answer_nonexisting=test")) + .andExpect(status().isBadRequest()); + } + + @Test + public void searchRightsByQuestionsAdditionalUnAuthorized() throws Exception { + + getClient().perform(get( + "/api/config/submissioncclicenseUrls/search/rightsByQuestions?license=license2&answer_license2-field0" + + "=license2-field0-enum1")) + .andExpect(status().isUnauthorized()); + + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java index dbe090f05e..e52b70b7bd 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionDefinitionsControllerIT.java @@ -206,7 +206,7 @@ public class SubmissionDefinitionsControllerIT extends AbstractControllerIntegra // We expect the content type to be "application/hal+json;charset=UTF-8" .andExpect(content().contentType(contentType)) // Match only that a section exists with a submission configuration behind - .andExpect(jsonPath("$._embedded.submissionsections", hasSize(5))) + .andExpect(jsonPath("$._embedded.submissionsections", hasSize(6))) .andExpect(jsonPath("$._embedded.submissionsections", Matchers.hasItem( allOf( diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/UUIDLookupRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/UUIDLookupRestControllerIT.java index 41e68f08c8..d8cad3117a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/UUIDLookupRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/UUIDLookupRestControllerIT.java @@ -237,7 +237,9 @@ public class UUIDLookupRestControllerIT extends AbstractControllerIntegrationTes String uuid = eperson.getID().toString(); String epersonDetail = REST_SERVER_URL + "eperson/epersons/" + uuid; - getClient().perform(get("/api/dso/find?uuid={uuid}",uuid)) + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(get("/api/dso/find?uuid={uuid}",uuid)) .andExpect(status().isFound()) //We expect a Location header to redirect to the eperson details .andExpect(header().string("Location", epersonDetail)); @@ -262,7 +264,9 @@ public class UUIDLookupRestControllerIT extends AbstractControllerIntegrationTes String uuid = group.getID().toString(); String groupDetail = REST_SERVER_URL + "eperson/groups/" + uuid; - getClient().perform(get("/api/dso/find?uuid={uuid}",uuid)) + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(get("/api/dso/find?uuid={uuid}",uuid)) .andExpect(status().isFound()) //We expect a Location header to redirect to the group details .andExpect(header().string("Location", groupDetail)); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionRestRepositoryIT.java index c158dde5f0..1a64454dc6 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/VersionRestRepositoryIT.java @@ -20,7 +20,10 @@ import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.InstallItemService; import org.dspace.content.service.ItemService; +import org.dspace.content.service.WorkspaceItemService; import org.dspace.services.ConfigurationService; import org.dspace.versioning.Version; import org.dspace.versioning.service.VersioningService; @@ -43,6 +46,12 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired private ConfigurationService configurationService; + @Autowired + private InstallItemService installItemService; + + @Autowired + private WorkspaceItemService workspaceItemService; + @Before public void setup() { context.turnOffAuthorisationSystem(); @@ -166,6 +175,10 @@ public class VersionRestRepositoryIT extends AbstractControllerIntegrationTest { @Test public void versionForItemTest() throws Exception { + context.turnOffAuthorisationSystem(); + WorkspaceItem workspaceItem = workspaceItemService.findByItem(context, version.getItem()); + installItemService.installItem(context, workspaceItem); + context.restoreAuthSystemState(); getClient().perform(get("/api/core/items/" + version.getItem().getID() + "/version")) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is(VersionMatcher.matchEntry(version)))); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java index 08b057d2c4..13837f6461 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkflowItemRestRepositoryIT.java @@ -1800,4 +1800,21 @@ public class WorkflowItemRestRepositoryIT extends AbstractControllerIntegrationT "Workflow Item 3", "2016-02-13"))) .andExpect(jsonPath("$._embedded.step", WorkflowStepMatcher.matchWorkflowStepEntry(step))); } + + @Test + public void discoverableNestedLinkTest() throws Exception { + String token = getAuthToken(eperson.getEmail(), password); + getClient(token).perform(get("/api")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._links",Matchers.allOf( + hasJsonPath("$.claimedtasks.href", + is("http://localhost/api/workflow/claimedtasks")), + hasJsonPath("$.claimedtask-search.href", + is("http://localhost/api/workflow/claimedtask/search")), + hasJsonPath("$.pooltasks.href", + is("http://localhost/api/workflow/pooltasks")), + hasJsonPath("$.pooltask-search.href", + is("http://localhost/api/workflow/pooltask/search")) + ))); + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/AdministratorFeatureIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/AdministratorFeatureIT.java index cb326d41c7..e1e89cda02 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/AdministratorFeatureIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/AdministratorFeatureIT.java @@ -15,7 +15,9 @@ import org.dspace.app.rest.authorization.impl.AdministratorOfFeature; 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.converter.ConverterService; +import org.dspace.app.rest.converter.CollectionConverter; +import org.dspace.app.rest.converter.CommunityConverter; +import org.dspace.app.rest.converter.SiteConverter; import org.dspace.app.rest.matcher.AuthorizationMatcher; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.CommunityRest; @@ -47,13 +49,17 @@ public class AdministratorFeatureIT extends AbstractControllerIntegrationTest { @Autowired private AuthorizationFeatureService authorizationFeatureService; @Autowired - private ConverterService converterService; - @Autowired GroupService groupService; @Autowired AuthorizeService authService; @Autowired CommunityService communityService; + @Autowired + private CommunityConverter communityConverter; + @Autowired + private CollectionConverter collectionConverter; + @Autowired + private SiteConverter siteConverter; private SiteService siteService; @@ -103,9 +109,10 @@ public class AdministratorFeatureIT extends AbstractControllerIntegrationTest { context.restoreAuthSystemState(); - CommunityRest communityRestA = converterService.toRest(communityA, DefaultProjection.DEFAULT); - CommunityRest SubCommunityOfArest = converterService.toRest(subCommunityOfA, DefaultProjection.DEFAULT); - CollectionRest collectionRestOfSubComm = converterService.toRest(collectionOfSubComm,DefaultProjection.DEFAULT); + CommunityRest communityRestA = communityConverter.convert(communityA, DefaultProjection.DEFAULT); + CommunityRest SubCommunityOfArest = communityConverter.convert(subCommunityOfA, DefaultProjection.DEFAULT); + CollectionRest collectionRestOfSubComm = collectionConverter.convert(collectionOfSubComm, + DefaultProjection.DEFAULT); // tokens String tokenAdminComA = getAuthToken(adminComA.getEmail(), password); @@ -170,8 +177,8 @@ public class AdministratorFeatureIT extends AbstractControllerIntegrationTest { context.restoreAuthSystemState(); - CollectionRest collectionRestA = converterService.toRest(collectionA, DefaultProjection.DEFAULT); - CollectionRest collectionRestB = converterService.toRest(collectionB, DefaultProjection.DEFAULT); + CollectionRest collectionRestA = collectionConverter.convert(collectionA, DefaultProjection.DEFAULT); + CollectionRest collectionRestB = collectionConverter.convert(collectionB, DefaultProjection.DEFAULT); String tokenAdminColA = getAuthToken(adminColA.getEmail(), password); String tokenAdminColB = getAuthToken(adminColB.getEmail(), password); @@ -210,9 +217,9 @@ public class AdministratorFeatureIT extends AbstractControllerIntegrationTest { context.restoreAuthSystemState(); Site site = siteService.findSite(context); - SiteRest siteRest = converterService.toRest(site, DefaultProjection.DEFAULT); - CommunityRest communityRest = converterService.toRest(parentCommunity, DefaultProjection.DEFAULT); - CollectionRest collectionRest = converterService.toRest(collection, DefaultProjection.DEFAULT); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); + CommunityRest communityRest = communityConverter.convert(parentCommunity, DefaultProjection.DEFAULT); + CollectionRest collectionRest = collectionConverter.convert(collection, DefaultProjection.DEFAULT); // tokens String tokenAdmin = getAuthToken(admin.getEmail(), password); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CCLicenseFeatureRestIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CCLicenseFeatureRestIT.java index 79000dc22d..97225d7df0 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CCLicenseFeatureRestIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/CCLicenseFeatureRestIT.java @@ -16,10 +16,10 @@ import org.dspace.app.rest.builder.CollectionBuilder; import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.builder.ItemBuilder; import org.dspace.app.rest.builder.ResourcePolicyBuilder; -import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.converter.ItemConverter; import org.dspace.app.rest.matcher.AuthorizationMatcher; import org.dspace.app.rest.model.ItemRest; -import org.dspace.app.rest.projection.DefaultProjection; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.utils.Utils; import org.dspace.authorize.ResourcePolicy; @@ -45,10 +45,10 @@ public class CCLicenseFeatureRestIT extends AbstractControllerIntegrationTest { private AuthorizationFeatureService authorizationFeatureService; @Autowired - private ConverterService converterService; + private ConfigurationService configurationService; @Autowired - private ConfigurationService configurationService; + private ItemConverter itemConverter; @Autowired private Utils utils; @@ -70,7 +70,7 @@ public class CCLicenseFeatureRestIT extends AbstractControllerIntegrationTest { Item item = ItemBuilder.createItem(context, col).withTitle("Item to withdraw").build(); context.restoreAuthSystemState(); - ItemRest itemRest = converterService.toRest(item, converterService.getProjection(DefaultProjection.NAME)); + ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); String itemUri = utils.linkToSingleResource(itemRest, "self").getHref(); Authorization authAdminCCLicense = new Authorization(admin, ccLicenseFeature, itemRest); @@ -99,7 +99,7 @@ public class CCLicenseFeatureRestIT extends AbstractControllerIntegrationTest { Item item = ItemBuilder.createItem(context, col).withTitle("Item to withdraw").build(); context.restoreAuthSystemState(); - ItemRest itemRest = converterService.toRest(item, converterService.getProjection(DefaultProjection.NAME)); + ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); String itemUri = utils.linkToSingleResource(itemRest, "self").getHref(); Authorization authAdminCCLicense = new Authorization(eperson, ccLicenseFeature, itemRest); @@ -159,7 +159,7 @@ public class CCLicenseFeatureRestIT extends AbstractControllerIntegrationTest { Item item = ItemBuilder.createItem(context, col).withTitle("Item to withdraw").build(); context.restoreAuthSystemState(); - ItemRest itemRest = converterService.toRest(item, converterService.getProjection(DefaultProjection.NAME)); + ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); String itemUri = utils.linkToSingleResource(itemRest, "self").getHref(); Authorization authAdminCCLicense = new Authorization(eperson, ccLicenseFeature, itemRest); @@ -200,7 +200,7 @@ public class CCLicenseFeatureRestIT extends AbstractControllerIntegrationTest { .withUser(eperson).withDspaceObject(item).build(); context.restoreAuthSystemState(); - ItemRest itemRest = converterService.toRest(item, converterService.getProjection(DefaultProjection.NAME)); + ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); String itemUri = utils.linkToSingleResource(itemRest, "self").getHref(); Authorization authAdminCCLicense = new Authorization(eperson, ccLicenseFeature, itemRest); @@ -238,7 +238,7 @@ public class CCLicenseFeatureRestIT extends AbstractControllerIntegrationTest { Item item = ItemBuilder.createItem(context, col).withTitle("Item to withdraw").build(); context.restoreAuthSystemState(); - ItemRest itemRest = converterService.toRest(item, converterService.getProjection(DefaultProjection.NAME)); + ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); String itemUri = utils.linkToSingleResource(itemRest, "self").getHref(); Authorization authEpersonCCLicense = new Authorization(eperson, ccLicenseFeature, itemRest); Authorization authAnonymousCCLicense = new Authorization(null, ccLicenseFeature, itemRest); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EnrollAdministratorIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EnrollAdministratorIT.java index ddc879947f..2be3ed9466 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EnrollAdministratorIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/EnrollAdministratorIT.java @@ -16,7 +16,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import org.dspace.app.rest.authorization.impl.AdministratorOfFeature; import org.dspace.app.rest.builder.EPersonBuilder; -import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.converter.SiteConverter; import org.dspace.app.rest.matcher.AuthorizationMatcher; import org.dspace.app.rest.model.SiteRest; import org.dspace.app.rest.projection.DefaultProjection; @@ -44,9 +44,9 @@ public class EnrollAdministratorIT extends AbstractControllerIntegrationTest { @Autowired private AuthorizationFeatureService authorizationFeatureService; @Autowired - private ConverterService converterService; - @Autowired GroupService groupService; + @Autowired + private SiteConverter siteConverter; private SiteService siteService; @@ -76,7 +76,7 @@ public class EnrollAdministratorIT extends AbstractControllerIntegrationTest { context.restoreAuthSystemState(); Site site = siteService.findSite(context); - SiteRest siteRest = converterService.toRest(site, DefaultProjection.DEFAULT); + SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT); Group adminGroup = groupService.findByName(context, Group.ADMIN); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ReinstateFeatureRestIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ReinstateFeatureRestIT.java index fb68e47a3a..96cd2243fc 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ReinstateFeatureRestIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/ReinstateFeatureRestIT.java @@ -17,10 +17,10 @@ import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.builder.ItemBuilder; import org.dspace.app.rest.builder.WorkflowItemBuilder; import org.dspace.app.rest.builder.WorkspaceItemBuilder; -import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.converter.ItemConverter; import org.dspace.app.rest.matcher.AuthorizationMatcher; import org.dspace.app.rest.model.ItemRest; -import org.dspace.app.rest.projection.DefaultProjection; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.utils.Utils; import org.dspace.content.Collection; @@ -46,7 +46,7 @@ public class ReinstateFeatureRestIT extends AbstractControllerIntegrationTest { private AuthorizationFeatureService authorizationFeatureService; @Autowired - private ConverterService converterService; + private ItemConverter itemConverter; @Autowired private ConfigurationService configurationService; @@ -71,7 +71,7 @@ public class ReinstateFeatureRestIT extends AbstractControllerIntegrationTest { Item item = ItemBuilder.createItem(context, col).withTitle("Withdrawn item").withdrawn().build(); context.restoreAuthSystemState(); - ItemRest itemRest = converterService.toRest(item, converterService.getProjection(DefaultProjection.NAME)); + ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); String itemUri = utils.linkToSingleResource(itemRest, "self").getHref(); Authorization authAdminWithdraw = new Authorization(admin, reinstateFeature, itemRest); @@ -100,7 +100,7 @@ public class ReinstateFeatureRestIT extends AbstractControllerIntegrationTest { Item item = ItemBuilder.createItem(context, col).withTitle("Withdrawn item").withdrawn().build(); context.restoreAuthSystemState(); - ItemRest itemRest = converterService.toRest(item, converterService.getProjection(DefaultProjection.NAME)); + ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); String itemUri = utils.linkToSingleResource(itemRest, "self").getHref(); Authorization authAdminWithdraw = new Authorization(eperson, reinstateFeature, itemRest); @@ -159,7 +159,7 @@ public class ReinstateFeatureRestIT extends AbstractControllerIntegrationTest { Item item = ItemBuilder.createItem(context, col).withTitle("Withdrawn item").withdrawn().build(); context.restoreAuthSystemState(); - ItemRest itemRest = converterService.toRest(item, converterService.getProjection(DefaultProjection.NAME)); + ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); String itemUri = utils.linkToSingleResource(itemRest, "self").getHref(); Authorization authAdminWithdraw = new Authorization(eperson, reinstateFeature, itemRest); @@ -197,7 +197,7 @@ public class ReinstateFeatureRestIT extends AbstractControllerIntegrationTest { Item item = ItemBuilder.createItem(context, col).withTitle("Withdrawn item").withdrawn().build(); context.restoreAuthSystemState(); - ItemRest itemRest = converterService.toRest(item, converterService.getProjection(DefaultProjection.NAME)); + ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); String itemUri = utils.linkToSingleResource(itemRest, "self").getHref(); Authorization authEpersonWithdraw = new Authorization(eperson, reinstateFeature, itemRest); Authorization authAnonymousWithdraw = new Authorization(null, reinstateFeature, itemRest); @@ -236,14 +236,11 @@ public class ReinstateFeatureRestIT extends AbstractControllerIntegrationTest { WorkflowItem wfItem = WorkflowItemBuilder.createWorkflowItem(context, col).withTitle("A workflow item").build(); context.restoreAuthSystemState(); - ItemRest archivedItemRest = converterService.toRest(archivedItem, - converterService.getProjection(DefaultProjection.NAME)); + ItemRest archivedItemRest = itemConverter.convert(archivedItem, Projection.DEFAULT); String archivedItemUri = utils.linkToSingleResource(archivedItemRest, "self").getHref(); - ItemRest wsItemRest = converterService.toRest(wsItem.getItem(), - converterService.getProjection(DefaultProjection.NAME)); + ItemRest wsItemRest = itemConverter.convert(wsItem.getItem(), Projection.DEFAULT); String wsItemUri = utils.linkToSingleResource(wsItemRest, "self").getHref(); - ItemRest wfItemRest = converterService.toRest(wfItem.getItem(), - converterService.getProjection(DefaultProjection.NAME)); + ItemRest wfItemRest = itemConverter.convert(wfItem.getItem(), Projection.DEFAULT); String wfItemUri = utils.linkToSingleResource(wfItemRest, "self").getHref(); Authorization authWithdrawnItem = new Authorization(admin, reinstateFeature, archivedItemRest); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/WithdrawFeatureRestIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/WithdrawFeatureRestIT.java index 631b273621..dac47a3a88 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/WithdrawFeatureRestIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/authorization/WithdrawFeatureRestIT.java @@ -17,10 +17,10 @@ import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.builder.ItemBuilder; import org.dspace.app.rest.builder.WorkflowItemBuilder; import org.dspace.app.rest.builder.WorkspaceItemBuilder; -import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.converter.ItemConverter; import org.dspace.app.rest.matcher.AuthorizationMatcher; import org.dspace.app.rest.model.ItemRest; -import org.dspace.app.rest.projection.DefaultProjection; +import org.dspace.app.rest.projection.Projection; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.utils.Utils; import org.dspace.content.Collection; @@ -46,7 +46,7 @@ public class WithdrawFeatureRestIT extends AbstractControllerIntegrationTest { private AuthorizationFeatureService authorizationFeatureService; @Autowired - private ConverterService converterService; + private ItemConverter itemConverter; @Autowired private ConfigurationService configurationService; @@ -71,7 +71,7 @@ public class WithdrawFeatureRestIT extends AbstractControllerIntegrationTest { Item item = ItemBuilder.createItem(context, col).withTitle("Item to withdraw").build(); context.restoreAuthSystemState(); - ItemRest itemRest = converterService.toRest(item, converterService.getProjection(DefaultProjection.NAME)); + ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); String itemUri = utils.linkToSingleResource(itemRest, "self").getHref(); Authorization authAdminWithdraw = new Authorization(admin, withdrawFeature, itemRest); @@ -100,7 +100,7 @@ public class WithdrawFeatureRestIT extends AbstractControllerIntegrationTest { Item item = ItemBuilder.createItem(context, col).withTitle("Item to withdraw").build(); context.restoreAuthSystemState(); - ItemRest itemRest = converterService.toRest(item, converterService.getProjection(DefaultProjection.NAME)); + ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); String itemUri = utils.linkToSingleResource(itemRest, "self").getHref(); Authorization authAdminWithdraw = new Authorization(eperson, withdrawFeature, itemRest); @@ -159,7 +159,7 @@ public class WithdrawFeatureRestIT extends AbstractControllerIntegrationTest { Item item = ItemBuilder.createItem(context, col).withTitle("Item to withdraw").build(); context.restoreAuthSystemState(); - ItemRest itemRest = converterService.toRest(item, converterService.getProjection(DefaultProjection.NAME)); + ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); String itemUri = utils.linkToSingleResource(itemRest, "self").getHref(); Authorization authAdminWithdraw = new Authorization(eperson, withdrawFeature, itemRest); @@ -197,7 +197,7 @@ public class WithdrawFeatureRestIT extends AbstractControllerIntegrationTest { Item item = ItemBuilder.createItem(context, col).withTitle("Item to withdraw").build(); context.restoreAuthSystemState(); - ItemRest itemRest = converterService.toRest(item, converterService.getProjection(DefaultProjection.NAME)); + ItemRest itemRest = itemConverter.convert(item, Projection.DEFAULT); String itemUri = utils.linkToSingleResource(itemRest, "self").getHref(); Authorization authEpersonWithdraw = new Authorization(eperson, withdrawFeature, itemRest); Authorization authAnonymousWithdraw = new Authorization(null, withdrawFeature, itemRest); @@ -237,14 +237,11 @@ public class WithdrawFeatureRestIT extends AbstractControllerIntegrationTest { WorkflowItem wfItem = WorkflowItemBuilder.createWorkflowItem(context, col).withTitle("A workflow item").build(); context.restoreAuthSystemState(); - ItemRest withdrawnItemRest = converterService.toRest(withdrawnItem, - converterService.getProjection(DefaultProjection.NAME)); + ItemRest withdrawnItemRest = itemConverter.convert(withdrawnItem, Projection.DEFAULT); String withdrawnItemUri = utils.linkToSingleResource(withdrawnItemRest, "self").getHref(); - ItemRest wsItemRest = converterService.toRest(wsItem.getItem(), - converterService.getProjection(DefaultProjection.NAME)); + ItemRest wsItemRest = itemConverter.convert(wsItem.getItem(), Projection.DEFAULT); String wsItemUri = utils.linkToSingleResource(wsItemRest, "self").getHref(); - ItemRest wfItemRest = converterService.toRest(wfItem.getItem(), - converterService.getProjection(DefaultProjection.NAME)); + ItemRest wfItemRest = itemConverter.convert(wfItem.getItem(), Projection.DEFAULT); String wfItemUri = utils.linkToSingleResource(wfItemRest, "self").getHref(); Authorization authWithdrawnItem = new Authorization(admin, withdrawFeature, withdrawnItemRest); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/GroupBuilder.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/GroupBuilder.java index 8208198b6d..5319a67718 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/GroupBuilder.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/builder/GroupBuilder.java @@ -67,6 +67,11 @@ public class GroupBuilder extends AbstractDSpaceObjectBuilder { @Override public Group build() { + try { + groupService.update(context, group); + } catch (Exception e) { + return handleException(e); + } return group; } 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 index 9c1a500290..5749a44b79 100644 --- 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 @@ -7,9 +7,11 @@ */ package org.dspace.app.rest.builder; +import java.io.IOException; import java.sql.SQLException; import java.util.List; +import org.dspace.authorize.AuthorizeException; import org.dspace.content.ProcessStatus; import org.dspace.core.Context; import org.dspace.eperson.EPerson; @@ -41,6 +43,7 @@ public class ProcessBuilder extends AbstractBuilder { return this; } + @Override public void cleanup() throws Exception { try (Context c = new Context()) { c.turnOffAuthorisationSystem(); @@ -75,4 +78,20 @@ public class ProcessBuilder extends AbstractBuilder { getService().delete(c, dso); } } + + public static void deleteProcess(Integer integer) throws SQLException, IOException { + try (Context c = new Context()) { + c.turnOffAuthorisationSystem(); + Process process = processService.find(c, integer); + if (process != null) { + try { + processService.delete(c, process); + } catch (AuthorizeException e) { + // cannot occur, just wrap it to make the compiler happy + throw new RuntimeException(e); + } + } + c.complete(); + } + } } 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 index 80e3c8be7f..685bd5dbfd 100644 --- 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 @@ -13,6 +13,7 @@ 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.mock; import static org.mockito.Mockito.when; import java.util.HashMap; @@ -31,11 +32,19 @@ 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.dspace.core.Context; +import org.dspace.services.RequestService; +import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.Link; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; /** * Tests functionality of {@link ConverterService}. @@ -57,6 +66,26 @@ public class ConverterServiceIT extends AbstractControllerIntegrationTest { @Mock private Object mockEmbeddedResource; + @Autowired + private RequestService requestService; + + @Before + public void setup() { + // We're mocking a request here because we've started using the Context in the ConverterService#toRest + // method by invoking the DSpacePermissionEvaluator. This will traverse the RestPermissionEvaluatorPlugins + // and thus also invoke the AdminRestPermissionEvaluator which will try to retrieve the Context from a + // Request. This Request isn't available through tests on itself and thus we have to mock it here to avoid + // the PermissionEvaluator from crashing because of this. + MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest(); + mockHttpServletRequest.setAttribute("dspace.context", new Context()); + MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse(); + requestService.startRequest(mockHttpServletRequest, mockHttpServletResponse); + Authentication authentication = mock(Authentication.class); + SecurityContext securityContext = mock(SecurityContext.class); + when(securityContext.getAuthentication()).thenReturn(authentication); + SecurityContextHolder.setContext(securityContext); + when(SecurityContextHolder.getContext().getAuthentication().getPrincipal()).thenReturn(eperson); + } /** * When calling {@code toRest} with an object for which an appropriate {@link DSpaceConverter} can't be found, * it should throw an {@link IllegalArgumentException}. @@ -83,6 +112,10 @@ public class ConverterServiceIT extends AbstractControllerIntegrationTest { /** * When calling {@code toRest} with the default projection, the converter should run and no changes should be made. + * This converter.toRest will now also check permissions through the PreAuthorize annotation on the + * Repository's findOne method. Therefor a repository has been added for this MockObjectRest namely + * {@link org.dspace.app.rest.repository.MockObjectRestRepository} and added PreAuthorize annotations + * on the methods of this Repository */ @Test public void toRestWithDefaultProjection() { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/RootConverterTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/RootConverterTest.java index 36124f9502..3cbda52272 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/RootConverterTest.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/converter/RootConverterTest.java @@ -48,9 +48,9 @@ public class RootConverterTest { public void testCorrectPropertiesSetFromConfigurationService() throws Exception { String restUrl = "rest"; RootRest rootRest = rootConverter.convert(); - assertEquals("dspaceurl", rootRest.getDspaceURL()); + assertEquals("dspaceurl", rootRest.getDspaceUI()); assertEquals("dspacename", rootRest.getDspaceName()); - assertEquals(restUrl, rootRest.getDspaceRest()); + assertEquals(restUrl, rootRest.getDspaceServer()); } @Test diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CSVMetadataImportReferenceIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CSVMetadataImportReferenceIT.java index baad6f0904..53f3966c7c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CSVMetadataImportReferenceIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/csv/CSVMetadataImportReferenceIT.java @@ -19,6 +19,8 @@ import java.util.Iterator; import java.util.List; import java.util.UUID; +import org.dspace.app.bulkedit.MetadataImportException; +import org.dspace.app.bulkedit.MetadataImportInvalidHeadingException; import org.dspace.app.rest.builder.CollectionBuilder; import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.builder.ItemBuilder; @@ -59,8 +61,8 @@ public class CSVMetadataImportReferenceIT extends AbstractEntityIntegrationTest public void setup() { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context) - .withName("Parent Community") - .build(); + .withName("Parent Community") + .build(); col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1").build(); context.restoreAuthSystemState(); } @@ -80,15 +82,15 @@ public class CSVMetadataImportReferenceIT extends AbstractEntityIntegrationTest int foundCount = 0; for (Relationship rel : rels) { if (rel.getRightItem().getID().equals(rightItem.getID()) - && rel.getLeftItem().getID().equals(leftItem.getID())) { + && rel.getLeftItem().getID().equals(leftItem.getID())) { foundCount++; relationship = rel; } } if (placeDirection.equalsIgnoreCase("left")) { - assertEquals(placeCount, relationship.getLeftPlace()); + assertEquals(relationship.getLeftPlace(), placeCount); } else { - assertEquals(placeCount, relationship.getRightPlace()); + assertEquals(relationship.getRightPlace(), placeCount); } assertEquals(expectedCount, foundCount); } @@ -100,8 +102,8 @@ public class CSVMetadataImportReferenceIT extends AbstractEntityIntegrationTest @Test public void testSingleMdRef() throws Exception { String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,dc.identifier.other", - "+,Person,," + col1.getHandle() + ",0", - "+,Publication,dc.identifier.other:0," + col1.getHandle() + ",1"}; + "+,Person,," + col1.getHandle() + ",0", + "+,Publication,dc.identifier.other:0," + col1.getHandle() + ",1"}; Item[] items = runImport(csv); assertRelationship(items[1], items[0], 1, "left", 0); } @@ -129,9 +131,9 @@ public class CSVMetadataImportReferenceIT extends AbstractEntityIntegrationTest @Test public void testSingleRowNameRef() throws Exception { String[] csv = {"id,dc.title,relationship.type,relation.isAuthorOfPublication,collection,rowName," + - "dc.identifier.other", - "+,Test Item 1,Person,," + col1.getHandle() + ",idVal,0", - "+,Test Item 2,Publication,rowName:idVal," + col1.getHandle() + ",anything,1"}; + "dc.identifier.other", + "+,Test Item 1,Person,," + col1.getHandle() + ",idVal,0", + "+,Test Item 2,Publication,rowName:idVal," + col1.getHandle() + ",anything,1"}; Item[] items = runImport(csv); assertRelationship(items[1], items[0], 1, "left", 0); } @@ -143,9 +145,9 @@ public class CSVMetadataImportReferenceIT extends AbstractEntityIntegrationTest @Test public void testMultiMdRef() throws Exception { String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,dc.identifier.other", - "+,Person,," + col1.getHandle() + ",0", - "+,Person,," + col1.getHandle() + ",1", - "+,Publication,dc.identifier.other:0||dc.identifier.other:1," + col1.getHandle() + ",2"}; + "+,Person,," + col1.getHandle() + ",0", + "+,Person,," + col1.getHandle() + ",1", + "+,Publication,dc.identifier.other:0||dc.identifier.other:1," + col1.getHandle() + ",2"}; Item[] items = runImport(csv); assertRelationship(items[2], items[0], 1, "left", 0); assertRelationship(items[2], items[1], 1, "left", 1); @@ -158,9 +160,9 @@ public class CSVMetadataImportReferenceIT extends AbstractEntityIntegrationTest @Test public void testMultiRowNameRef() throws Exception { String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,dc.identifier.other,rowName", - "+,Person,," + col1.getHandle() + ",0,val1", - "+,Person,," + col1.getHandle() + ",1,val2", - "+,Publication,rowName:val1||rowName:val2," + col1.getHandle() + ",2,val3"}; + "+,Person,," + col1.getHandle() + ",0,val1", + "+,Person,," + col1.getHandle() + ",1,val2", + "+,Publication,rowName:val1||rowName:val2," + col1.getHandle() + ",2,val3"}; Item[] items = runImport(csv); assertRelationship(items[2], items[0], 1, "left", 0); assertRelationship(items[2], items[1], 1, "left", 1); @@ -174,11 +176,11 @@ public class CSVMetadataImportReferenceIT extends AbstractEntityIntegrationTest public void testSingleUUIDReference() throws Exception { context.turnOffAuthorisationSystem(); Item person = ItemBuilder.createItem(context, col1) - .withRelationshipType("Person") - .build(); + .withRelationshipType("Person") + .build(); context.restoreAuthSystemState(); String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,rowName,dc.identifier.other", - "+,Publication," + person.getID().toString() + "," + col1.getHandle() + ",anything,0"}; + "+,Publication," + person.getID().toString() + "," + col1.getHandle() + ",anything,0"}; Item[] items = runImport(csv); assertRelationship(items[0], person, 1, "left", 0); } @@ -191,15 +193,15 @@ public class CSVMetadataImportReferenceIT extends AbstractEntityIntegrationTest public void testMultiUUIDReference() throws Exception { context.turnOffAuthorisationSystem(); Item person = ItemBuilder.createItem(context, col1) - .withRelationshipType("Person") - .build(); + .withRelationshipType("Person") + .build(); Item person2 = ItemBuilder.createItem(context, col1) - .withRelationshipType("Person") - .build(); + .withRelationshipType("Person") + .build(); context.restoreAuthSystemState(); String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,rowName,dc.identifier.other", - "+,Publication," + person.getID().toString() + "||" + person2.getID().toString() + "," + - col1.getHandle() + ",anything,0"}; + "+,Publication," + person.getID().toString() + "||" + person2.getID().toString() + "," + + col1.getHandle() + ",anything,0"}; Item[] items = runImport(csv); assertRelationship(items[0], person, 1, "left", 0); assertRelationship(items[0], person2, 1, "left", 1); @@ -213,13 +215,13 @@ public class CSVMetadataImportReferenceIT extends AbstractEntityIntegrationTest public void testMultiRefArchivedCsv() throws Exception { context.turnOffAuthorisationSystem(); Item person = ItemBuilder.createItem(context, col1) - .withTitle("Person") - .withRelationshipType("Person") - .build(); + .withTitle("Person") + .withRelationshipType("Person") + .build(); String[] csv = {"id,dc.title,relationship.type,relation.isAuthorOfPublication,collection,rowName," + - "dc.identifier.other", - "+,Person2,Person,," + col1.getHandle() + ",idVal,0", - "+,Pub1,Publication,dc.title:Person||dc.title:Person2," + col1.getHandle() + ",anything,1"}; + "dc.identifier.other", + "+,Person2,Person,," + col1.getHandle() + ",idVal,0", + "+,Pub1,Publication,dc.title:Person||dc.title:Person2," + col1.getHandle() + ",anything,1"}; context.restoreAuthSystemState(); Item[] items = runImport(csv); assertRelationship(items[1], person, 1, "left", 0); @@ -235,19 +237,19 @@ public class CSVMetadataImportReferenceIT extends AbstractEntityIntegrationTest public void testMultiMixedRefArchivedCsv() throws Exception { context.turnOffAuthorisationSystem(); Item person = ItemBuilder.createItem(context, col1) - .withTitle("Person") - .withRelationshipType("Person") - .build(); + .withTitle("Person") + .withRelationshipType("Person") + .build(); Item person2 = ItemBuilder.createItem(context, col1) - .withTitle("Person2") - .withRelationshipType("Person") - .build(); + .withTitle("Person2") + .withRelationshipType("Person") + .build(); context.restoreAuthSystemState(); String[] csv = {"id,dc.title,relationship.type,relation.isAuthorOfPublication,collection,rowName," + - "dc.identifier.other", - "+,Person3,Person,," + col1.getHandle() + ",idVal,0", - "+,Pub1,Publication," + person.getID() + "||dc.title:Person2||rowName:idVal," + - col1.getHandle() + ",anything,1"}; + "dc.identifier.other", + "+,Person3,Person,," + col1.getHandle() + ",idVal,0", + "+,Pub1,Publication," + person.getID() + "||dc.title:Person2||rowName:idVal," + + col1.getHandle() + ",anything,1"}; Item[] items = runImport(csv); assertRelationship(items[1], person, 1, "left", 0); assertRelationship(items[1], person2, 1, "left", 1); @@ -261,9 +263,9 @@ public class CSVMetadataImportReferenceIT extends AbstractEntityIntegrationTest @Test public void testRefWithSpecialChar() throws Exception { String[] csv = {"id,dc.title,relationship.type,relation.isAuthorOfPublication,collection,rowName," + - "dc.identifier.other", - "+,Person:,Person,," + col1.getHandle() + ",idVal,0", - "+,Pub1,Publication,dc.title:Person:," + col1.getHandle() + ",anything,1"}; + "dc.identifier.other", + "+,Person:,Person,," + col1.getHandle() + ",idVal,0", + "+,Pub1,Publication,dc.title:Person:," + col1.getHandle() + ",anything,1"}; Item[] items = runImport(csv); assertRelationship(items[1], items[0], 1, "left", 0); } @@ -271,138 +273,138 @@ public class CSVMetadataImportReferenceIT extends AbstractEntityIntegrationTest /** * Test failure when referring to item by non unique metadata in the csv file. */ - @Test + @Test(expected = MetadataImportException.class) public void testNonUniqueMDRefInCsv() throws Exception { String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,dc.identifier.other", - "+,Person,," + col1.getHandle() + ",1", - "+,Person,," + col1.getHandle() + ",1", - "+,Publication,dc.identifier.other:1," + col1.getHandle() + ",2"}; - assertEquals(1, performImportScript(csv, true)); + "+,Person,," + col1.getHandle() + ",1", + "+,Person,," + col1.getHandle() + ",1", + "+,Publication,dc.identifier.other:1," + col1.getHandle() + ",2"}; + performImportScript(csv, true); } /** * Test failure when referring to item by non unique metadata in the csv file. */ - @Test + @Test(expected = MetadataImportException.class) public void testNonUniqueRowName() throws Exception { String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,dc.identifier.other,rowName", - "+,Person,," + col1.getHandle() + ",1,value", - "+,Person,," + col1.getHandle() + ",1,value", - "+,Publication,rowName:value," + col1.getHandle() + ",2"}; - assertEquals(1, performImportScript(csv, true)); + "+,Person,," + col1.getHandle() + ",1,value", + "+,Person,," + col1.getHandle() + ",1,value", + "+,Publication,rowName:value," + col1.getHandle() + ",2"}; + performImportScript(csv, true); } /** * Test failure when referring to item by non unique metadata in the database. */ - @Test + @Test(expected = MetadataImportException.class) public void testNonUniqueMDRefInDb() throws Exception { context.turnOffAuthorisationSystem(); ItemBuilder.createItem(context, col1) - .withRelationshipType("Person") - .withIdentifierOther("1") - .build(); + .withRelationshipType("Person") + .withIdentifierOther("1") + .build(); ItemBuilder.createItem(context, col1) - .withRelationshipType("Person") - .withIdentifierOther("1") - .build(); + .withRelationshipType("Person") + .withIdentifierOther("1") + .build(); context.restoreAuthSystemState(); String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,dc.identifier.other", - "+,Publication,dc.identifier.other:1," + col1.getHandle() + ",2"}; - assertEquals(1, performImportScript(csv, true)); + "+,Publication,dc.identifier.other:1," + col1.getHandle() + ",2"}; + performImportScript(csv, true); } /** * Test failure when referring to item by non unique metadata in the csv and database. */ - @Test + @Test(expected = MetadataImportException.class) public void testNonUniqueMDRefInBoth() throws Exception { context.turnOffAuthorisationSystem(); ItemBuilder.createItem(context, col1) - .withRelationshipType("Person") - .withIdentifierOther("1") - .build(); + .withRelationshipType("Person") + .withIdentifierOther("1") + .build(); context.restoreAuthSystemState(); String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,dc.identifier.other", - "+,Person,," + col1.getHandle() + ",1", - "+,Publication,dc.identifier.other:1," + col1.getHandle() + ",2"}; - assertEquals(1, performImportScript(csv, true)); + "+,Person,," + col1.getHandle() + ",1", + "+,Publication,dc.identifier.other:1," + col1.getHandle() + ",2"}; + performImportScript(csv, true); } /** * Test failure when refering to item by metadata that does not exist in the relation column */ - @Test + @Test(expected = Exception.class) public void testNonExistMdRef() throws Exception { String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,dc.identifier.other", - "+,Person,," + col1.getHandle() + ",1", - "+,Publication,dc.identifier.other:8675309," + col1.getHandle() + ",2"}; - assertEquals(1, performImportScript(csv, false)); + "+,Person,," + col1.getHandle() + ",1", + "+,Publication,dc.identifier.other:8675309," + col1.getHandle() + ",2"}; + performImportScript(csv, false); } /** * Test failure when refering to an item in the CSV that hasn't been created yet due to it's order in the CSV */ - @Test + @Test(expected = Exception.class) public void testCSVImportWrongOrder() throws Exception { String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,dc.identifier.other", - "+,Publication,dc.identifier.other:8675309," + col1.getHandle() + ",2", - "+,Person,," + col1.getHandle() + ",8675309",}; - assertEquals(1, performImportScript(csv, false)); + "+,Publication,dc.identifier.other:8675309," + col1.getHandle() + ",2", + "+,Person,," + col1.getHandle() + ",8675309",}; + performImportScript(csv, false); } /** * Test failure when refering to an item in the CSV that hasn't been created yet due to it's order in the CSV */ - @Test + @Test(expected = Exception.class) public void testCSVImportWrongOrderRowName() throws Exception { String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,dc.identifier.other,rowName", - "+,Publication,rowName:row2," + col1.getHandle() + ",2,row1", - "+,Person,," + col1.getHandle() + ",8675309,row2",}; - assertEquals(1, performImportScript(csv, false)); + "+,Publication,rowName:row2," + col1.getHandle() + ",2,row1", + "+,Person,," + col1.getHandle() + ",8675309,row2",}; + performImportScript(csv, false); } /** * Test relationship validation with invalid relationship definition */ - @Test + @Test(expected = MetadataImportException.class) public void testCSVImportInvalidRelationship() throws Exception { String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,rowName", - "+,Publication,," + col1.getHandle() + ",row1", - "+,Unit,rowName:row1," + col1.getHandle() + ",row2",}; - assertEquals(1, performImportScript(csv, true)); + "+,Publication,," + col1.getHandle() + ",row1", + "+,Unit,rowName:row1," + col1.getHandle() + ",row2",}; + performImportScript(csv, true); } /** * Test relationship validation with invalid relationship definition and with an archived origin referer */ - @Test + @Test(expected = MetadataImportInvalidHeadingException.class) public void testInvalidRelationshipArchivedOrigin() throws Exception { context.turnOffAuthorisationSystem(); Item testItem = ItemBuilder.createItem(context, col1) - .withRelationshipType("OrgUnit") - .build(); + .withRelationshipType("OrgUnit") + .build(); context.restoreAuthSystemState(); String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,rowName", - "+,Person,," + col1.getHandle() + ",1" + + "+,Person,," + col1.getHandle() + ",1" + testItem.getID().toString() + ",,rowName:1," + col1.getHandle() + ",2"}; - assertEquals(1, performImportScript(csv, false)); + performImportScript(csv, false); } /** * Test relationship validation with invalid relationship definition and with archived target reference */ - @Test + @Test(expected = MetadataImportInvalidHeadingException.class) public void testInvalidRelationshipArchivedTarget() throws Exception { context.turnOffAuthorisationSystem(); Item testItem = ItemBuilder.createItem(context, col1) - .withRelationshipType("OrgUnit") - .build(); + .withRelationshipType("OrgUnit") + .build(); context.restoreAuthSystemState(); String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,rowName", - testItem.getID().toString() + ",Person,," + col1.getHandle() + ",1" + + testItem.getID().toString() + ",Person,," + col1.getHandle() + ",1" + "+,OrgUnit,rowName:1," + col1.getHandle() + ",2"}; - assertEquals(1, performImportScript(csv, false)); + performImportScript(csv, false); } /** @@ -412,22 +414,22 @@ public class CSVMetadataImportReferenceIT extends AbstractEntityIntegrationTest public void testValidRelationshipNoDefinedTypesInCSV() throws Exception { context.turnOffAuthorisationSystem(); Item testItemOne = ItemBuilder.createItem(context, col1) - .withRelationshipType("Person") - .withIdentifierOther("testItemOne") - .build(); + .withRelationshipType("Person") + .withIdentifierOther("testItemOne") + .build(); Item testItemTwo = ItemBuilder.createItem(context, col1) - .withRelationshipType("Publication") - .withIdentifierOther("testItemTwo") - .build(); + .withRelationshipType("Publication") + .withIdentifierOther("testItemTwo") + .build(); Item testItemThree = ItemBuilder.createItem(context, col1) - .withRelationshipType("Project") - .withIdentifierOther("testItemThree") - .build(); + .withRelationshipType("Project") + .withIdentifierOther("testItemThree") + .build(); context.restoreAuthSystemState(); String[] csv = {"id,relation.isAuthorOfPublication,relation.isPublicationOfProject,collection", - testItemOne.getID().toString() + ",,," + col1.getHandle(), - testItemTwo.getID().toString() + ",dc.identifier.other:testItemOne,," + col1.getHandle(), - testItemThree.getID().toString() + ",,dc.identifier.other:testItemTwo," + col1.getHandle()}; + testItemOne.getID().toString() + ",,," + col1.getHandle(), + testItemTwo.getID().toString() + ",dc.identifier.other:testItemOne,," + col1.getHandle(), + testItemThree.getID().toString() + ",,dc.identifier.other:testItemTwo," + col1.getHandle()}; performImportScript(csv, false); assertRelationship(testItemTwo, testItemOne, 1, "left", 0); assertRelationship(testItemTwo, testItemThree, 1, "left", 0); @@ -439,9 +441,9 @@ public class CSVMetadataImportReferenceIT extends AbstractEntityIntegrationTest @Test public void testDuplicateRowNameReferences() throws Exception { String[] csv = {"id,relationship.type,relation.isAuthorOfPublication,collection,dc.identifier.other,rowName", - "+,Person,," + col1.getHandle() + ",0,value", - "+,Publication,rowName:value," + col1.getHandle() + ",1,1", - "+,Publication,rowName:value," + col1.getHandle() + ",2,2"}; + "+,Person,," + col1.getHandle() + ",0,value", + "+,Publication,rowName:value," + col1.getHandle() + ",1,1", + "+,Publication,rowName:value," + col1.getHandle() + ",2,2"}; Item[] items = runImport(csv); assertRelationship(items[1], items[0], 1, "left", 0); assertRelationship(items[2], items[0], 1, "left", 0); @@ -450,18 +452,18 @@ public class CSVMetadataImportReferenceIT extends AbstractEntityIntegrationTest /** * Test relationship validation with invalid relationship definition by incorrect typeName usage */ - @Test + @Test(expected = MetadataImportException.class) public void testInvalidTypeNameDefined() throws Exception { context.turnOffAuthorisationSystem(); Item testItem = ItemBuilder.createItem(context, col1) - .withRelationshipType("Publication") - .build(); + .withRelationshipType("Publication") + .build(); context.restoreAuthSystemState(); String[] csv = {"id,collection,relationship.type,dc.title," + - "relation.isProjectOfPublication,relation.isPublicationOfProject", - "+," + col1.getHandle() + ",Project,Title," + + "relation.isProjectOfPublication,relation.isPublicationOfProject", + "+," + col1.getHandle() + ",Project,Title," + testItem.getID().toString() + "," + testItem.getID().toString() }; - assertEquals(1, performImportScript(csv, true)); + performImportScript(csv, true); } /** @@ -478,10 +480,10 @@ public class CSVMetadataImportReferenceIT extends AbstractEntityIntegrationTest try { if (validateOnly) { return runDSpaceScript("metadata-import", "-f", csvFile.getAbsolutePath(), "-e", "admin@email.com", - "-s", "-v"); + "-s", "-v"); } else { return runDSpaceScript("metadata-import", "-f", csvFile.getAbsolutePath(), "-e", "admin@email.com", - "-s"); + "-s"); } } finally { csvFile.delete(); @@ -499,7 +501,7 @@ public class CSVMetadataImportReferenceIT extends AbstractEntityIntegrationTest ArrayList uuidList = new ArrayList<>(); MetadataValueService metadataValueService = ContentServiceFactory.getInstance().getMetadataValueService(); MetadataFieldService metadataFieldService = - ContentServiceFactory.getInstance().getMetadataFieldService(); + ContentServiceFactory.getInstance().getMetadataFieldService(); MetadataField mfo = metadataFieldService.findByElement(context, "dc", "identifier", "other"); Iterator mdv = metadataValueService.findByFieldAndValue(context, mfo, value); while (mdv.hasNext()) { 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 d9497f182b..a545e857d6 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 @@ -116,7 +116,7 @@ public class BitstreamMatcher { ); } - private static Matcher matchProperties(Bitstream bitstream) { + public static Matcher matchProperties(Bitstream bitstream) { try { return allOf( hasJsonPath("$.uuid", is(bitstream.getID().toString())), diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ProcessFileTypesMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ProcessFileTypesMatcher.java new file mode 100644 index 0000000000..cc34a0479f --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/ProcessFileTypesMatcher.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.app.rest.matcher; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.is; + +import java.util.List; +import java.util.stream.Collectors; + +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; + +public class ProcessFileTypesMatcher { + + private ProcessFileTypesMatcher() { + } + + public static Matcher matchProcessFileTypes(String id, List filetypes) { + return allOf( + hasJsonPath("$.id", is(id)), + hasJsonPath("$.values", Matchers.containsInAnyOrder( + filetypes.stream().map(Matchers::containsString) + .collect(Collectors.toList()) + )) + ); + + } +} 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 index 2ac00eb4ab..cc62c1e9ab 100644 --- 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 @@ -65,7 +65,10 @@ public class ProcessMatcher { list.stream().map(dSpaceCommandLineParameter -> ParameterValueMatcher .matchParameterValue(dSpaceCommandLineParameter.getName(), dSpaceCommandLineParameter.getValue())) .collect(Collectors.toList()) - )) + )), + hasJsonPath("$._links.script.href", Matchers.containsString(name)), + hasJsonPath("$._links.files.href", Matchers.containsString("files")), + hasJsonPath("$._links.self.href", Matchers.containsString("api/system/processes")) ); } 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 index d348ee76c4..c919aadd86 100644 --- 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 @@ -32,7 +32,8 @@ public class ScriptMatcher { matchScript("mock-script", "Mocking a script for testing purposes"), hasJsonPath("$.parameters", Matchers.containsInAnyOrder( ParameterMatcher.matchParameter(options.getOption("r")), - ParameterMatcher.matchParameter(options.getOption("i")) + ParameterMatcher.matchParameter(options.getOption("i")), + ParameterMatcher.matchParameter(options.getOption("f")) )) ); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubmissionCCLicenseMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubmissionCCLicenseMatcher.java new file mode 100644 index 0000000000..cdf0470b51 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SubmissionCCLicenseMatcher.java @@ -0,0 +1,82 @@ +/** + * 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.containsInAnyOrder; +import static org.hamcrest.Matchers.is; + +import java.util.LinkedList; +import java.util.List; + +import org.hamcrest.Matcher; + +public class SubmissionCCLicenseMatcher { + + private SubmissionCCLicenseMatcher() { + } + + public static Matcher matchLicenseEntry(int count, int[] amountOfFieldsAndEnums) { + return allOf( + matchLicenseProperties(count), + matchFields(count, amountOfFieldsAndEnums) + ); + } + + private static Matcher matchFields(int count, int[] amountOfFieldsAndEnums) { + List> matchers = new LinkedList<>(); + for (int index = 0; index < amountOfFieldsAndEnums.length; index++) { + matchers.add(matchField(count, index, amountOfFieldsAndEnums[index])); + } + return hasJsonPath("$.fields", containsInAnyOrder(matchers)); + } + + private static Matcher matchField(int count, int fieldIndex, int amountOfEnums) { + return allOf( + matchLicenseFieldProperties(count, fieldIndex), + matchEnums(count, fieldIndex, amountOfEnums) + ); + + } + + private static Matcher matchEnums(int count, int fieldIndex, int amountOfEnums) { + List> matchers = new LinkedList<>(); + for (int index = 0; index < amountOfEnums; index++) { + matchers.add(matchLicenseFieldEnumProperties(count, fieldIndex, index)); + } +// return hasJsonPath("$.enums"); + return hasJsonPath("$.enums", containsInAnyOrder(matchers)); + } + + + public static Matcher matchLicenseProperties(int count) { + return allOf( + hasJsonPath("$.id", is("license" + count)), + hasJsonPath("$.name", is("License " + count + " - Name")) + ); + } + + public static Matcher matchLicenseFieldProperties(int count, int fieldIndex) { + return allOf( + hasJsonPath("$.id", is("license" + count + "-field" + fieldIndex)), + hasJsonPath("$.label", is("License " + count + " - Field " + fieldIndex + " - Label")), + hasJsonPath("$.description", is("License " + count + " - Field " + fieldIndex + " - Description")) + ); + } + + public static Matcher matchLicenseFieldEnumProperties(int count, int fieldIndex, int enumIndex) { + return allOf( + hasJsonPath("$.id", is("license" + count + "-field" + fieldIndex + "-enum" + enumIndex)), + hasJsonPath("$.label", + is("License " + count + " - Field " + fieldIndex + " - Enum " + enumIndex + " - Label")), + hasJsonPath("$.description", + is("License " + count + " - Field " + fieldIndex + " - Enum " + enumIndex + " - " + "Description")) + ); + } +} 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 index 02080251ac..0b9babda68 100644 --- 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 @@ -34,6 +34,6 @@ abstract class AbstractMockObjectChildLinkRepository children.add(MockObject.create(102)); } Pageable pageable = utils.getPageable(optionalPageable); - return converter.toRestPage(children, pageable, children.size(), projection); + return converter.toRestPage(children, pageable, projection); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectRestRepository.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectRestRepository.java new file mode 100644 index 0000000000..0c54811249 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/repository/MockObjectRestRepository.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.app.rest.repository; + +import org.dspace.app.rest.model.MockObjectRest; +import org.dspace.core.Context; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * This class has been added to allow the MockObjectRest to act as an actual BaseObjectRest since they're + * expected to have a RestRepository + */ +@Component(MockObjectRest.CATEGORY + "." + MockObjectRest.NAME) +public class MockObjectRestRepository extends DSpaceRestRepository { + + // Added a permitAll preAuthorize annotation to allow the object to be used in tests by every user + @Override + @PreAuthorize("permitAll()") + public MockObjectRest findOne(Context context, Long aLong) { + return null; + } + + @Override + @PreAuthorize("permitAll()") + public Page findAll(Context context, Pageable pageable) { + return null; + } + + @Override + public Class getDomainClass() { + return MockObjectRest.class; + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/MockObjectRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/MockObjectRestPermissionEvaluatorPlugin.java new file mode 100644 index 0000000000..c77ba3bef8 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/MockObjectRestPermissionEvaluatorPlugin.java @@ -0,0 +1,27 @@ +/** + * 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 org.apache.commons.lang3.StringUtils; +import org.dspace.app.rest.model.MockObjectRest; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +@Component +public class MockObjectRestPermissionEvaluatorPlugin extends RestObjectPermissionEvaluatorPlugin { + @Override + public boolean hasDSpacePermission(Authentication authentication, Serializable targetId, String targetType, + DSpaceRestPermission restPermission) { + if (!StringUtils.equalsIgnoreCase(MockObjectRest.NAME, targetType)) { + return false; + } + return true; + } +} 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 index 74e2a56668..1b5b3fa7ac 100644 --- 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 @@ -33,4 +33,4 @@ public class TestDSpaceRunnableHandler extends CommandLineDSpaceRunnableHandler public Exception getException() { return exception; } -} \ No newline at end of file +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/license/MockCCLicenseConnectorServiceImpl.java b/dspace-server-webapp/src/test/java/org/dspace/license/MockCCLicenseConnectorServiceImpl.java new file mode 100644 index 0000000000..bb443ab4a4 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/license/MockCCLicenseConnectorServiceImpl.java @@ -0,0 +1,127 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.license; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.jdom.Document; +import org.jdom.JDOMException; + +/** + * Mock implementation for the Creative commons license connector service. + * This class will return a structure of CC Licenses similar to the CC License API but without having to contact it + */ +public class MockCCLicenseConnectorServiceImpl extends CCLicenseConnectorServiceImpl { + + /** + * Retrieves mock CC Licenses for the provided language + * @param language - the language + * @return a map of mocked licenses with the id and the license + */ + public Map retrieveLicenses(String language) { + Map ccLicenses = new HashMap<>(); + CCLicense mockLicense1 = createMockLicense(1, new int[]{3, 2, 3}); + CCLicense mockLicense2 = createMockLicense(2, new int[]{2}); + CCLicense mockLicense3 = createMockLicense(3, new int[]{}); + + ccLicenses.put(mockLicense1.getLicenseId(), mockLicense1); + ccLicenses.put(mockLicense2.getLicenseId(), mockLicense2); + ccLicenses.put(mockLicense3.getLicenseId(), mockLicense3); + + return ccLicenses; + } + + private CCLicense createMockLicense(int count, int[] amountOfFieldsAndEnums) { + String licenseId = "license" + count; + String licenseName = "License " + count + " - Name"; + List mockLicenseFields = createMockLicenseFields(count, amountOfFieldsAndEnums); + return new CCLicense(licenseId, licenseName, mockLicenseFields); + } + + private List createMockLicenseFields(int count, int[] amountOfFieldsAndEnums) { + List ccLicenseFields = new LinkedList<>(); + for (int index = 0; index < amountOfFieldsAndEnums.length; index++) { + String licenseFieldId = "license" + count + "-field" + index; + String licenseFieldLabel = "License " + count + " - Field " + index + " - Label"; + String licenseFieldDescription = "License " + count + " - Field " + index + " - Description"; + List mockLicenseFields = createMockLicenseFields(count, + index, + amountOfFieldsAndEnums[index]); + ccLicenseFields.add(new CCLicenseField(licenseFieldId, + licenseFieldLabel, + licenseFieldDescription, + mockLicenseFields)); + + } + + return ccLicenseFields; + } + + private List createMockLicenseFields(int count, int index, int amountOfEnums) { + List ccLicenseFieldEnumList = new LinkedList<>(); + for (int i = 0; i < amountOfEnums; i++) { + String enumId = "license" + count + "-field" + index + "-enum" + i; + String enumLabel = "License " + count + " - Field " + index + " - Enum " + i + " - Label"; + String enumDescription = "License " + count + " - Field " + index + " - Enum " + i + " - " + + "Description"; + ccLicenseFieldEnumList.add(new CCLicenseFieldEnum(enumId, enumLabel, enumDescription)); + } + return ccLicenseFieldEnumList; + + } + + /** + * Retrieve a mock CC License URI + * + * @param licenseId - the ID of the license + * @param language - the language for which to retrieve the full answerMap + * @param answerMap - the answers to the different field questions + * @return the CC License URI + */ + public String retrieveRightsByQuestion(final String licenseId, + final String language, + final Map answerMap) { + + return "mock-license-uri"; + } + + /** + * Retrieve a mock license RDF document. + * When the uri contains "invalid", null will be returned to simulate that no document was found for the provided + * URI + * + * @param licenseURI - The license URI for which to retrieve the license RDF document + * @return a mock license RDF document or null when the URI contains invalid + * @throws IOException + */ + public Document retrieveLicenseRDFDoc(String licenseURI) throws IOException { + if (!StringUtils.contains(licenseURI, "invalid")) { + InputStream cclicense = null; + try { + cclicense = getClass().getResourceAsStream("cc-license-rdf.xml"); + + Document doc = parser.build(cclicense); + return doc; + } catch (JDOMException e) { + throw new RuntimeException(e); + } finally { + if (cclicense != null) { + cclicense.close(); + } + } + } + return null; + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/scripts/MockDSpaceRunnableScriptConfiguration.java b/dspace-server-webapp/src/test/java/org/dspace/scripts/MockDSpaceRunnableScriptConfiguration.java new file mode 100644 index 0000000000..1197370e32 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/scripts/MockDSpaceRunnableScriptConfiguration.java @@ -0,0 +1,68 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.scripts; + +import java.io.InputStream; +import java.sql.SQLException; + +import org.apache.commons.cli.Options; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.core.Context; +import org.dspace.scripts.configuration.ScriptConfiguration; +import org.dspace.scripts.impl.MockDSpaceRunnableScript; +import org.springframework.beans.factory.annotation.Autowired; + +public class MockDSpaceRunnableScriptConfiguration extends ScriptConfiguration { + + + @Autowired + private AuthorizeService authorizeService; + + private Class dspaceRunnableClass; + + @Override + public Class getDspaceRunnableClass() { + return dspaceRunnableClass; + } + + /** + * Generic setter for the dspaceRunnableClass + * @param dspaceRunnableClass The dspaceRunnableClass to be set on this MetadataExportScriptConfiguration + */ + @Override + public void setDspaceRunnableClass(Class dspaceRunnableClass) { + this.dspaceRunnableClass = dspaceRunnableClass; + } + + @Override + public boolean isAllowedToExecute(Context context) { + try { + return authorizeService.isAdmin(context); + } catch (SQLException e) { + throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e); + } + } + + @Override + public Options getOptions() { + if (options == null) { + 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); + options.addOption("f", "file", true, "source file"); + options.getOption("f").setType(InputStream.class); + options.getOption("f").setRequired(false); + super.options = options; + } + return options; + } +} 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 index 5df5c8992b..960927e90a 100644 --- 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 @@ -7,19 +7,20 @@ */ package org.dspace.scripts.impl; -import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.dspace.scripts.DSpaceRunnable; +import org.dspace.scripts.MockDSpaceRunnableScriptConfiguration; +import org.dspace.utils.DSpace; -public class MockDSpaceRunnableScript extends DSpaceRunnable { - - private MockDSpaceRunnableScript() { - Options options = constructOptions(); - this.options = options; +public class MockDSpaceRunnableScript extends DSpaceRunnable { + @Override + public void internalRun() throws Exception { } @Override - public void internalRun() throws Exception { + public MockDSpaceRunnableScriptConfiguration getScriptConfiguration() { + return new DSpace().getServiceManager() + .getServiceByName("mock-script", MockDSpaceRunnableScriptConfiguration.class); } @Override @@ -28,15 +29,4 @@ public class MockDSpaceRunnableScript extends DSpaceRunnable { 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-server-webapp/src/test/resources/org/dspace/license/cc-license-rdf.xml b/dspace-server-webapp/src/test/resources/org/dspace/license/cc-license-rdf.xml new file mode 100644 index 0000000000..5ff75ee4c7 --- /dev/null +++ b/dspace-server-webapp/src/test/resources/org/dspace/license/cc-license-rdf.xml @@ -0,0 +1,31 @@ + + + http://creativecommons.org/licenses/by-nc-sa/4.0/ + Attribution-NonCommercial-ShareAlike 4.0 International + false + + + + + + + + + + + + + + + + + + + + + + + + + Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. +
diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 5090f5ea34..1bbd895131 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -28,7 +28,7 @@ dspace.server.url = http://localhost:8080/server # URL of DSpace frontend (Angular UI). Include port number etc # This is used by the backend to provide links in emails, RSS feeds, Sitemaps, etc. -dspace.ui.url = http://localhost:3000 +dspace.ui.url = http://localhost:4000 # Name of the site dspace.name = DSpace at My University @@ -785,6 +785,7 @@ registry.metadata.load = schema-organization-types.xml registry.metadata.load = schema-periodical-types.xml registry.metadata.load = schema-publicationIssue-types.xml registry.metadata.load = schema-publicationVolume-types.xml +registry.metadata.load = dspace-types.xml diff --git a/dspace/config/item-submission.xml b/dspace/config/item-submission.xml index af707162ae..25162341e4 100644 --- a/dspace/config/item-submission.xml +++ b/dspace/config/item-submission.xml @@ -115,8 +115,8 @@ - @@ -203,6 +203,10 @@ + + + + diff --git a/dspace/config/launcher.xml b/dspace/config/launcher.xml index 3c4f205c2a..f06225e5cc 100644 --- a/dspace/config/launcher.xml +++ b/dspace/config/launcher.xml @@ -152,20 +152,6 @@ net.handle.server.SimpleSetup - - metadata-export - Export metadata for batch editing - - org.dspace.app.bulkedit.MetadataExport - - - - metadata-import - Import metadata after batch editing - - org.dspace.app.bulkedit.MetadataImport - - migrate-embargo Embargo manager tool used to migrate old version of Embargo to the new one included in dspace3 diff --git a/dspace/config/local.cfg.EXAMPLE b/dspace/config/local.cfg.EXAMPLE index 9754da9566..221d54daf4 100644 --- a/dspace/config/local.cfg.EXAMPLE +++ b/dspace/config/local.cfg.EXAMPLE @@ -38,7 +38,7 @@ dspace.server.url = http://localhost:8080/server # URL of DSpace frontend (Angular UI). Include port number etc # This is used by the backend to provide links in emails, RSS feeds, Sitemaps, etc. -dspace.ui.url = http://localhost:3000 +dspace.ui.url = http://localhost:4000 # Name of the site dspace.name = DSpace at My University diff --git a/dspace/config/modules/rest.cfg b/dspace/config/modules/rest.cfg index 266c065f3a..31a927256e 100644 --- a/dspace/config/modules/rest.cfg +++ b/dspace/config/modules/rest.cfg @@ -161,3 +161,9 @@ rest.report-regex-non-ascii = ^.*[^\\p{ASCII}].*$ # The maximum number of results to return for 1 request rest.search.max.results = 100 + +# Define which configuration properties are exposed through the http:///api/config/properties/ +# rest endpoint. If a rest request is made for a property which exists, but isn't listed here, the server will +# respond that the property wasn't found. This property can be defined multiple times to allow access to multiple +# configuration properties. +rest.properties.exposed = google.analytics.key diff --git a/dspace/config/registries/dspace-types.xml b/dspace/config/registries/dspace-types.xml new file mode 100644 index 0000000000..56985373f0 --- /dev/null +++ b/dspace/config/registries/dspace-types.xml @@ -0,0 +1,20 @@ + + + + DSpace Internal Types Registry + + + + dspace + http://dspace.org/dspace + + + + dspace + process + filetype + + + + + diff --git a/dspace/config/spring/api/core-services.xml b/dspace/config/spring/api/core-services.xml index 316f81fd18..74bdbf85da 100644 --- a/dspace/config/spring/api/core-services.xml +++ b/dspace/config/spring/api/core-services.xml @@ -44,6 +44,7 @@ + @@ -100,6 +101,7 @@ + diff --git a/dspace/config/spring/api/discovery.xml b/dspace/config/spring/api/discovery.xml index 2ebeb80cc4..803d1cad81 100644 --- a/dspace/config/spring/api/discovery.xml +++ b/dspace/config/spring/api/discovery.xml @@ -30,6 +30,7 @@ + diff --git a/dspace/config/spring/api/scripts.xml b/dspace/config/spring/api/scripts.xml index 8284edc05a..0713baad19 100644 --- a/dspace/config/spring/api/scripts.xml +++ b/dspace/config/spring/api/scripts.xml @@ -4,10 +4,18 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> - - + + + + + + + + + + diff --git a/dspace/config/spring/rest/scripts.xml b/dspace/config/spring/rest/scripts.xml new file mode 100644 index 0000000000..04cacb4930 --- /dev/null +++ b/dspace/config/spring/rest/scripts.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/dspace/solr/search/conf/schema.xml b/dspace/solr/search/conf/schema.xml index cf7dbc43dc..e16e213135 100644 --- a/dspace/solr/search/conf/schema.xml +++ b/dspace/solr/search/conf/schema.xml @@ -254,11 +254,15 @@ + + + + diff --git a/dspace/src/main/docker-compose/README.md b/dspace/src/main/docker-compose/README.md index 7f26cb7eab..9c92f627b6 100644 --- a/dspace/src/main/docker-compose/README.md +++ b/dspace/src/main/docker-compose/README.md @@ -18,7 +18,7 @@ - Sets the environment used across containers run with docker-compose - docker-compose-angular.yml - Docker compose file that will start a published DSpace angular container that interacts with the branch. -- environment.dev.js +- environment.dev.ts - Default angular environment when testing DSpace-angular from this repo ## To refresh / pull DSpace images from Dockerhub diff --git a/dspace/src/main/docker-compose/docker-compose-angular.yml b/dspace/src/main/docker-compose/docker-compose-angular.yml index d371ef4cc2..7eb78c5c07 100644 --- a/dspace/src/main/docker-compose/docker-compose-angular.yml +++ b/dspace/src/main/docker-compose/docker-compose-angular.yml @@ -17,17 +17,17 @@ services: environment: DSPACE_HOST: dspace-angular DSPACE_NAMESPACE: / - DSPACE_PORT: '3000' + DSPACE_PORT: '4000' DSPACE_SSL: "false" image: dspace/dspace-angular:latest networks: dspacenet: {} ports: - - published: 3000 - target: 3000 + - published: 4000 + target: 4000 - published: 9876 target: 9876 stdin_open: true tty: true volumes: - - ./dspace/src/main/docker-compose/environment.dev.js:/app/config/environment.dev.js + - ./dspace/src/main/docker-compose/environment.dev.ts:/app/src/environments/environment.dev.ts diff --git a/dspace/src/main/docker-compose/environment.dev.js b/dspace/src/main/docker-compose/environment.dev.ts similarity index 70% rename from dspace/src/main/docker-compose/environment.dev.js rename to dspace/src/main/docker-compose/environment.dev.ts index f88506012f..573c8ebb67 100644 --- a/dspace/src/main/docker-compose/environment.dev.js +++ b/dspace/src/main/docker-compose/environment.dev.ts @@ -1,11 +1,13 @@ -/* +/** * 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/ */ -module.exports = { +// This file is based on environment.template.ts provided by Angular UI +export const environment = { + // Default to using the local REST API (running in Docker) rest: { ssl: false, host: 'localhost', diff --git a/dspace/src/main/docker-compose/local.cfg b/dspace/src/main/docker-compose/local.cfg index 70bc45c112..a511c25789 100644 --- a/dspace/src/main/docker-compose/local.cfg +++ b/dspace/src/main/docker-compose/local.cfg @@ -1,5 +1,6 @@ dspace.dir=/dspace db.url=jdbc:postgresql://dspacedb:5432/dspace dspace.server.url=http://localhost:8080/server +dspace.ui.url=http://localhost:4000 dspace.name=DSpace Started with Docker Compose solr.server=http://dspacesolr:8983/solr diff --git a/dspace/src/main/docker/local.cfg b/dspace/src/main/docker/local.cfg index 480e05372f..28a4f68734 100644 --- a/dspace/src/main/docker/local.cfg +++ b/dspace/src/main/docker/local.cfg @@ -5,4 +5,5 @@ dspace.dir = /dspace db.url = jdbc:postgresql://dspacedb:5432/dspace dspace.server.url=http://localhost:8080/server +dspace.ui.url=http://localhost:4000 solr.server=http://dspacesolr:8983/solr diff --git a/pom.xml b/pom.xml index 9bceb6c1d9..aa8a8b15dd 100644 --- a/pom.xml +++ b/pom.xml @@ -391,7 +391,7 @@ src/** + http://mycila.mathieu.photography/license-maven-plugin/ --> true @@ -399,21 +399,19 @@ **/src/test/resources/** **/src/test/data/** **/src/main/license/** - **/test.cfg **/META-INF/** **/robots.txt - **/*.LICENSE **/LICENSE* **/README* **/readme* **/.gitignore - **/src/main/resources/rebel.xml + **/*.cfg - + SCRIPT_STYLE - TEXT + JAVADOC_STYLE UTF-8