Merge branch 'master' of https://github.com/DSpace/DSpace into CST-3091

This commit is contained in:
Andrea Bollini
2020-06-29 23:38:16 +02:00
128 changed files with 7532 additions and 2133 deletions

9
.lgtm.yml Normal file
View File

@@ -0,0 +1,9 @@
# LGTM Settings (https://lgtm.com/)
# For reference, see https://lgtm.com/help/lgtm/lgtm.yml-configuration-file
# or template at https://lgtm.com/static/downloads/lgtm.template.yml
extraction:
java:
index:
# Specify the Java version required to build the project
java_version: 11

View File

@@ -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);
}
/**

View File

@@ -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<Item> toExport;
public class MetadataExport extends DSpaceRunnable<MetadataExportScriptConfiguration> {
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<Item> 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<Item> buildFromCommunity(Context context, Community community, int indent)
throws SQLException {
// Add all the collections
List<Collection> collections = community.getCollections();
Iterator<Item> result = null;
for (Collection collection : collections) {
for (int i = 0; i < indent; i++) {
System.out.print(" ");
}
Iterator<Item> items = itemService.findByCollection(context, collection);
result = addItemsToResult(result, items);
}
// Add all the sub-communities
List<Community> communities = community.getSubcommunities();
for (Community subCommunity : communities) {
for (int i = 0; i < indent; i++) {
System.out.print(" ");
}
Iterator<Item> 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<Item> addItemsToResult(Iterator<Item> result, Iterator<Item> 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<Item> 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> 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');
}
}

View File

@@ -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<T extends MetadataExport> extends ScriptConfiguration<T> {
@Autowired
private AuthorizeService authorizeService;
private Class<T> dspaceRunnableClass;
@Override
public Class<T> getDspaceRunnableClass() {
return dspaceRunnableClass;
}
/**
* Generic setter for the dspaceRunnableClass
* @param dspaceRunnableClass The dspaceRunnableClass to be set on this MetadataExportScriptConfiguration
*/
@Override
public void setDspaceRunnableClass(Class<T> 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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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<MetadataImportCLI> {
}

View File

@@ -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<T extends MetadataImport> extends ScriptConfiguration<T> {
@Autowired
private AuthorizeService authorizeService;
private Class<T> dspaceRunnableClass;
@Override
public Class<T> getDspaceRunnableClass() {
return dspaceRunnableClass;
}
/**
* Generic setter for the dspaceRunnableClass
* @param dspaceRunnableClass The dspaceRunnableClass to be set on this MetadataImportScriptConfiguration
*/
@Override
public void setDspaceRunnableClass(Class<T> 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;
}
}

View File

@@ -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) {

View File

@@ -10,6 +10,7 @@ package org.dspace.app.util;
import java.util.List;
import java.util.Map;
import org.dspace.core.Utils;
/**
* Class representing all DC inputs required for a submission, organized into pages
*
@@ -107,9 +108,21 @@ public class DCInputSet {
for (int i = 0; i < inputs.length; i++) {
for (int j = 0; j < inputs[i].length; j++) {
DCInput field = inputs[i][j];
String fullName = field.getFieldName();
if (fullName.equals(fieldName)) {
return true;
// If this is a "qualdrop_value" field, then the full field name is the field + dropdown qualifier
if (field.getInputType().equals("qualdrop_value")) {
List<String> pairs = field.getPairs();
for (int k = 0; k < pairs.size(); k += 2) {
String qualifier = pairs.get(k + 1);
String fullName = Utils.standardize(field.getSchema(), field.getElement(), qualifier, ".");
if (fullName.equals(fieldName)) {
return true;
}
}
} else {
String fullName = field.getFieldName();
if (fullName.equals(fieldName)) {
return true;
}
}
}
}

View File

@@ -250,12 +250,8 @@ public class IndexVersion {
} else if (firstMinor > secondMinor) {
// If we get here, major versions must be EQUAL. Now, time to check our minor versions
return GREATER_THAN;
} else if (firstMinor < secondMinor) {
return LESS_THAN;
} else {
// This is an impossible scenario.
// This 'else' should never be triggered since we've checked for equality above already
return EQUAL;
return LESS_THAN;
}
}

View File

@@ -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<Item> 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> 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<Item> 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<Item> buildFromCommunity(Context context, Community community)
throws SQLException {
// Add all the collections
List<Collection> collections = community.getCollections();
Iterator<Item> result = null;
for (Collection collection : collections) {
Iterator<Item> items = itemService.findByCollection(context, collection);
result = addItemsToResult(result, items);
}
// Add all the sub-communities
List<Community> communities = community.getSubcommunities();
for (Community subCommunity : communities) {
Iterator<Item> items = buildFromCommunity(context, subCommunity);
result = addItemsToResult(result, items);
}
return result;
}
private Iterator<Item> addItemsToResult(Iterator<Item> result, Iterator<Item> items) {
if (result == null) {
result = items;
} else {
result = Iterators.concat(result, items);
}
return result;
}
}

View File

@@ -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<Item> 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;
}

View File

@@ -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<IndexDiscoveryScriptConfiguration> {
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.
*

View File

@@ -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<T extends IndexClient> extends ScriptConfiguration<T> {
@Autowired
private AuthorizeService authorizeService;
private Class<T> dspaceRunnableClass;
@Override
public Class<T> 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<T> dspaceRunnableClass) {
this.dspaceRunnableClass = dspaceRunnableClass;
}
}

View File

@@ -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<CCLicenseField> ccLicenseFieldList;
public CCLicense() {
super();
}
public CCLicense(String licenseId, String licenseName, int order) {
public CCLicense(String licenseId, String licenseName, List<CCLicenseField> 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<CCLicenseField> 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<CCLicenseField> ccLicenseFieldList) {
this.ccLicenseFieldList = ccLicenseFieldList;
}
}

View File

@@ -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<String, CCLicense> 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<String, String> 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);
}

View File

@@ -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 =
"<answers> " +
"<locale>{1}</locale>" +
"<license-{0}>" +
"{2}" +
"</license-{0}>" +
"</answers>";
@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<String, CCLicense> retrieveLicenses(String language) {
String ccLicenseUrl = configurationService.getProperty("cc.api.rooturl");
String uri = ccLicenseUrl + "/?locale=" + language;
HttpGet httpGet = new HttpGet(uri);
List<String> 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<String, CCLicense> 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<String> retrieveLicenses(CloseableHttpResponse response)
throws IOException, JaxenException, JDOMException {
List<String> 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<Element> 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<CCLicenseField> ccLicenseFields = new LinkedList<>();
List<Element> 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<Element> enums = enumXpath.selectNodes(licenseField);
List<CCLicenseFieldEnum> 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<String, String> 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<String, String> parameterMap) {
StringBuilder sb = new StringBuilder();
for (String key : parameterMap.keySet()) {
sb.append("<");
sb.append(key);
sb.append(">");
sb.append(parameterMap.get(key));
sb.append("</");
sb.append(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;
}
}

View File

@@ -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<CCLicenseFieldEnum> 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<CCLicenseFieldEnum> 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<String, String> getEnum() {
return this.fieldEnum;
public List<CCLicenseFieldEnum> getFieldEnum() {
return fieldEnum;
}
}

View File

@@ -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;
}
}

View File

@@ -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<String> lcFilter = new ArrayList<String>();
private Document license_doc = null;
private String rdfString = null;
private String errorMessage = null;
private boolean success = false;
private SAXBuilder parser = new SAXBuilder();
private List<CCLicense> licenses = new ArrayList<CCLicense>();
private List<CCLicenseField> licenseFields = new ArrayList<CCLicenseField>();
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<CCLicense> 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<Element> 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<CCLicenseField> 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 = "<answers>\n<locale>" + lang + "</locale>\n" + "<license-" + licenseId + ">\n";
Iterator keys = answers.keySet().iterator();
try {
String current = (String) keys.next();
while (true) {
answer_doc += "<" + current + ">" + (String) answers.get(current) + "</" + current + ">\n";
current = (String) keys.next();
}
} catch (NoSuchElementException e) {
// exception indicates we've iterated through the
// entire collection; just swallow and continue
}
// answer_doc += "<jurisdiction></jurisidiction>\n"; FAILS with jurisdiction argument
answer_doc += "</license-" + licenseId + ">\n</answers>\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;
}
}

View File

@@ -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<String, Map<String, CCLicense>> 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<Bundle> 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<Bundle> 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<Bundle> 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<CCLicense> 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<CCLicense> 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<String, CCLicense> 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<String, CCLicense> 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<String, String> 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<String, String> 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<String, String> 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<String, String> fullAnswerMap) {
CCLicense ccLicense = findOne(licenseId, language);
List<CCLicenseField> 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<String, String> retrieveFullAnswerMap(String licenseId, Map<String, String> 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<String, String> retrieveFullAnswerMap(String licenseId, String language, Map<String, String> answerMap) {
CCLicense ccLicense = findOne(licenseId, language);
if (ccLicense == null) {
return null;
}
Map<String, String> fullParamMap = new HashMap<>(answerMap);
List<CCLicenseField> 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<String, String> fullParamMap) {
if (fullParamMap.containsKey(JURISDICTION_KEY)) {
fullParamMap.put(JURISDICTION_KEY, jurisdiction);
}
}
private boolean containsAnswerEnum(final String enumAnswer, final CCLicenseField ccLicenseField) {
List<CCLicenseFieldEnum> 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<CCLicenseField> 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;
}
}

View File

@@ -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<MetadataValue> 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<MetadataValue> 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<MetadataValue> dcvalues = itemService.getMetadata(item, params[0], params[1], params[2], params[3]);
ArrayList<String> arrayList = new ArrayList<String>();
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);
}
}

View File

@@ -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<CCLicense> 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<CCLicense> 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<String, String> 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<String, String> 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<String, String> retrieveFullAnswerMap(String licenseId, Map<String, String> 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<String, String> retrieveFullAnswerMap(String licenseId, String language, Map<String, String> 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<String, String> 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<String, String> 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;
}

View File

@@ -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 <T>
*/
public abstract class DSpaceRunnable implements Runnable {
public abstract class DSpaceRunnable<T extends ScriptConfiguration> 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<String> getFileNamesFromInputStreamOptions() {
List<String> 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;
}
}

View File

@@ -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<Integer> {
@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<Integer> {
* @return The Bitstreams that are used or created by the process
*/
public List<Bitstream> getBitstreams() {
if (bitstreams == null) {
bitstreams = new LinkedList<>();
}
return bitstreams;
}

View File

@@ -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<DSpaceCommandLineParameter> 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<Bitstream> 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<Bitstream> getBitstreams(Context context, Process process) {
return process.getBitstreams();
}
public int countTotal(Context context) throws SQLException {
return processDAO.countRows(context);
}
@Override
public List<String> getFileTypesForProcessBitstreams(Context context, Process process) {
List<Bitstream> list = getBitstreams(context, process);
Set<String> fileTypesSet = new HashSet<>();
for (Bitstream bitstream : list) {
List<MetadataValue> 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);
}
}

View File

@@ -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<DSpaceRunnable> 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<DSpaceRunnable> getDSpaceRunnables(Context context) {
return dSpaceRunnables.stream().filter(
dSpaceRunnable -> dSpaceRunnable.isAllowedToExecute(context)).collect(Collectors.toList());
public List<ScriptConfiguration> 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);
}
}
}

View File

@@ -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<T extends DSpaceRunnable> 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<T> getDspaceRunnableClass();
/**
* Generic setter for the dspaceRunnableClass
* @param dspaceRunnableClass The dspaceRunnableClass to be set on this IndexDiscoveryScriptConfiguration
*/
public abstract void setDspaceRunnableClass(Class<T> 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;
}
}

View File

@@ -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<InputStream> 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;
}

View File

@@ -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<InputStream> 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);
}
}

View File

@@ -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<DSpaceCommandLineParameter> 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<Bitstream> 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<String> getFileTypesForProcessBitstreams(Context context, Process process);
}

View File

@@ -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<DSpaceRunnable> getDSpaceRunnables(Context context);
List<ScriptConfiguration> 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;
}

View File

@@ -0,0 +1,2 @@
id,collection,dc.contributor.author
+,"123456789/2","Donald, SmithImported"
1 id collection dc.contributor.author
2 + 123456789/2 Donald, SmithImported

View File

@@ -82,9 +82,9 @@
<!--Step will be to select a Creative Commons License -->
<!-- Uncomment this step to allow the user to select a Creative Commons
license -->
<!-- <step id="creative-commons"> <heading>submit.progressbar.CClicense</heading>
<processing-class>org.dspace.submit.step.CCLicenseStep</processing-class>
<type>cclicense</type> </step> -->
<step id="cclicense"> <heading>submit.progressbar.CClicense</heading>
<processing-class>org.dspace.app.rest.submit.step.CCLicenseStep</processing-class>
<type>cclicense</type> </step>
<!--Step will be to Check for potential duplicate -->
<!-- <step id="detect-duplicate"> <heading>submit.progressbar.detect.duplicate</heading>
@@ -145,7 +145,7 @@
<!--Step will be to Sign off on the License -->
<step id="license"/>
<!-- <step id="creative-commons"/> -->
<step id="cclicense"/>
<!-- <step id="verify"/> -->
</submission-process>

View File

@@ -4,13 +4,23 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="indexClient" class="org.dspace.discovery.IndexClient" scope="prototype">
<property name="name" value="index-discovery"/>
<bean id="index-discovery" class="org.dspace.discovery.IndexDiscoveryScriptConfiguration">
<property name="description" value="Update Discovery Solr Search Index"/>
<property name="dspaceRunnableClass" value="org.dspace.discovery.IndexClient"/>
</bean>
<bean id="MockScript" class="org.dspace.scripts.impl.MockDSpaceRunnableScript" scope="prototype">
<property name="name" value="mock-script" />
<bean id="metadata-import" class="org.dspace.app.bulkedit.MetadataImportCliScriptConfiguration">
<property name="description" value="Import metadata after batch editing" />
<property name="dspaceRunnableClass" value="org.dspace.app.bulkedit.MetadataImportCLI"/>
</bean>
<bean id="metadata-export" class="org.dspace.app.bulkedit.MetadataExportScriptConfiguration">
<property name="description" value="Export metadata for batch editing"/>
<property name="dspaceRunnableClass" value="org.dspace.app.bulkedit.MetadataExport"/>
</bean>
<bean id="mock-script" class="org.dspace.scripts.MockDSpaceRunnableScriptConfiguration" scope="prototype">
<property name="description" value="Mocking a script for testing purposes" />
<property name="dspaceRunnableClass" value="org.dspace.scripts.impl.MockDSpaceRunnableScript"/>
</bean>
</beans>

View File

@@ -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");

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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<String, CCLicense> retrieveLicenses(String language) {
Map<String, CCLicense> 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<CCLicenseField> mockLicenseFields = createMockLicenseFields(count, amountOfFieldsAndEnums);
return new CCLicense(licenseId, licenseName, mockLicenseFields);
}
private List<CCLicenseField> createMockLicenseFields(int count, int[] amountOfFieldsAndEnums) {
List<CCLicenseField> 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<CCLicenseFieldEnum> mockLicenseFields = createMockLicenseFields(count,
index,
amountOfFieldsAndEnums[index]);
ccLicenseFields.add(new CCLicenseField(licenseFieldId,
licenseFieldLabel,
licenseFieldDescription,
mockLicenseFields));
}
return ccLicenseFields;
}
private List<CCLicenseFieldEnum> createMockLicenseFields(int count, int index, int amountOfEnums) {
List<CCLicenseFieldEnum> 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<String, String> 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;
}
}

View File

@@ -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<T extends MockDSpaceRunnableScript> extends ScriptConfiguration<T> {
@Autowired
private AuthorizeService authorizeService;
private Class<T> dspaceRunnableClass;
@Override
public Class<T> getDspaceRunnableClass() {
return dspaceRunnableClass;
}
/**
* Generic setter for the dspaceRunnableClass
* @param dspaceRunnableClass The dspaceRunnableClass to be set on this MetadataExportScriptConfiguration
*/
@Override
public void setDspaceRunnableClass(Class<T> 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;
}
}

View File

@@ -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<MockDSpaceRunnableScriptConfiguration> {
@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;
}
}

View File

@@ -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

View File

@@ -274,16 +274,16 @@ public class CollectionsResource extends Resource {
headers, request, context);
items = new ArrayList<Item>();
Iterator<org.dspace.content.Item> dspaceItems = itemService.findByCollection(context, dspaceCollection);
for (int i = 0; (dspaceItems.hasNext()) && (i < (limit + offset)); i++) {
Iterator<org.dspace.content.Item> dspaceItems = itemService.findByCollection(context, dspaceCollection,
limit, offset);
while (dspaceItems.hasNext()) {
org.dspace.content.Item dspaceItem = dspaceItems.next();
if (i >= offset) {
if (itemService.isItemListedForUser(context, dspaceItem)) {
items.add(new Item(dspaceItem, servletContext, expand, context));
writeStats(dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor,
headers, request, context);
}
if (itemService.isItemListedForUser(context, dspaceItem)) {
items.add(new Item(dspaceItem, servletContext, expand, context));
writeStats(dspaceItem, UsageEvent.Action.VIEW, user_ip, user_agent, xforwardedfor,
headers, request, context);
}
}

View File

@@ -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;
*

View File

@@ -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;
@@ -27,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
@@ -56,13 +60,14 @@ public class ScriptProcessesController {
*/
@RequestMapping(method = RequestMethod.POST)
@PreAuthorize("hasAuthority('ADMIN')")
public ResponseEntity<RepresentationModel<?>> startProcess(@PathVariable(name = "name") String scriptName)
public ResponseEntity<RepresentationModel<?>> startProcess(@PathVariable(name = "name") String scriptName,
@RequestParam(name = "file") List<MultipartFile> files)
throws Exception {
if (log.isTraceEnabled()) {
log.trace("Starting Process for Script with name: " + scriptName);
}
Context context = ContextUtil.obtainContext(requestService.getCurrentRequest().getServletRequest());
ProcessRest processRest = scriptRestRepository.startProcess(context, scriptName);
ProcessRest processRest = scriptRestRepository.startProcess(context, scriptName, files);
ProcessResource processResource = converter.toResource(processRest);
context.complete();
return ControllerUtils.toResponseEntity(HttpStatus.ACCEPTED, new HttpHeaders(), processResource);

View File

@@ -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<SubmissionCCLicenseUrlRest, String>
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<String, String[]> requestParameterMap = servletRequest
.getParameterMap();
Map<String, String> 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<String, String> 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<SubmissionCCLicenseUrlRest> findAll(final Context context, final Pageable pageable) {
throw new RepositoryMethodNotImplementedException(SubmissionCCLicenseUrlRest.NAME, "findAll");
}
public Class<SubmissionCCLicenseUrlRest> getDomainClass() {
return SubmissionCCLicenseUrlRest.class;
}
@Override
public void afterPropertiesSet() {
discoverableEndpointsService.register(this, Arrays.asList(
new Link("/api/" + SubmissionCCLicenseUrlRest.CATEGORY + "/" +
SubmissionCCLicenseUrlRest.NAME + "/search",
SubmissionCCLicenseUrlRest.NAME + "-search")));
}
}

View File

@@ -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<DSpaceRunnable, ScriptRest> {
public class ScriptConverter implements DSpaceConverter<ScriptConfiguration, ScriptRest> {
@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<ParameterRest> 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<DSpaceRunnable, ScriptRe
}
@Override
public Class<DSpaceRunnable> getModelClass() {
return DSpaceRunnable.class;
public Class<ScriptConfiguration> getModelClass() {
return ScriptConfiguration.class;
}
}

View File

@@ -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<CCLicense, SubmissionCCLicenseRest> {
@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<CCLicenseField> ccLicenseFieldList = modelObject.getCcLicenseFieldList();
List<SubmissionCCLicenseFieldRest> submissionCCLicenseFieldRests = new LinkedList<>();
if (ccLicenseFieldList != null) {
for (CCLicenseField ccLicenseField : ccLicenseFieldList) {
submissionCCLicenseFieldRests.add(converter.toRest(ccLicenseField, projection));
}
}
submissionCCLicenseRest.setFields(submissionCCLicenseFieldRests);
return submissionCCLicenseRest;
}
@Override
public Class<CCLicense> getModelClass() {
return CCLicense.class;
}
}

View File

@@ -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<CCLicenseField, SubmissionCCLicenseFieldRest> {
@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<CCLicenseFieldEnum> fieldEnum = modelObject.getFieldEnum();
List<SubmissionCCLicenseFieldEnumRest> submissionCCLicenseFieldEnumRests = new LinkedList<>();
if (fieldEnum != null) {
for (CCLicenseFieldEnum ccLicenseFieldEnum : fieldEnum) {
submissionCCLicenseFieldEnumRests.add(converter.toRest(ccLicenseFieldEnum, projection));
}
}
submissionCCLicenseFieldRest.setEnums(submissionCCLicenseFieldEnumRests);
return submissionCCLicenseFieldRest;
}
@Override
public Class<CCLicenseField> getModelClass() {
return CCLicenseField.class;
}
}

View File

@@ -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<CCLicenseFieldEnum, SubmissionCCLicenseFieldEnumRest> {
/**
* 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<CCLicenseFieldEnum> getModelClass() {
return CCLicenseFieldEnum.class;
}
}

View File

@@ -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<SubmissionCCLicenseUrl, SubmissionCCLicenseUrlRest> {
/**
* 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<SubmissionCCLicenseUrl> getModelClass() {
return SubmissionCCLicenseUrl.class;
}
}

View File

@@ -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);
}

View File

@@ -0,0 +1,19 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.link.process;
import org.dspace.app.rest.RestResourceController;
import org.dspace.app.rest.link.HalLinkFactory;
/**
* This abstract class offers an easily extendable HalLinkFactory class to use methods on the RestResourceController
* and make it more easy to read or define which methods should be found in the getMethodOn methods when building links
* @param <T> This parameter should be of type {@link org.dspace.app.rest.model.hateoas.HALResource}
*/
public abstract class ProcessHalLinkFactory<T> extends HalLinkFactory<T, RestResourceController> {
}

View File

@@ -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,14 +18,15 @@ 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<ProcessResource, RestResourceController> {
public class ProcessResourceHalLinkFactory extends ProcessHalLinkFactory<ProcessResource> {
@Autowired
private ConfigurationService configurationService;
@Override
protected void addLinks(ProcessResource halResource, Pageable pageable, LinkedList<Link> list) throws Exception {
String dspaceServerUrl = configurationService.getProperty("dspace.server.url");
list.add(
@@ -34,10 +34,12 @@ public class ProcessResourceHalLinkFactory extends HalLinkFactory<ProcessResourc
}
@Override
protected Class<RestResourceController> getControllerClass() {
return RestResourceController.class;
}
@Override
protected Class<ProcessResource> getResourceClass() {
return ProcessResource.class;
}

View File

@@ -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<SubmissionCCLicenseUrlResource, RestResourceController> {
@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<Link> list)
throws Exception {
halResource.removeLinks();
Map<String, String[]> 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<RestResourceController> getControllerClass() {
return RestResourceController.class;
}
@Override
protected Class<SubmissionCCLicenseUrlResource> getResourceClass() {
return SubmissionCCLicenseUrlResource.class;
}
}

View File

@@ -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;
}

View File

@@ -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<String> {
public static final String NAME = "filetypes";
public static final String PLURAL_NAME = "filetypes";
public static final String CATEGORY = RestAddressableModel.SYSTEM;
private List<String> values;
/**
* Generic getter for the values
* @return the values value of this ProcessFileTypesRest
*/
public List<String> getValues() {
return values;
}
/**
* Generic setter for the values
* @param values The values to be set on this ProcessFileTypesRest
*/
public void setValues(List<String> 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;
}
}

View File

@@ -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<Integer> {
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;
}

View File

@@ -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;
}
}

View File

@@ -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<SubmissionCCLicenseFieldEnumRest> 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<SubmissionCCLicenseFieldEnumRest> getEnums() {
return enums;
}
public void setEnums(final List<SubmissionCCLicenseFieldEnumRest> enums) {
this.enums = enums;
}
}

View File

@@ -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<String> {
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<SubmissionCCLicenseFieldRest> 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<SubmissionCCLicenseFieldRest> getFields() {
return fields;
}
public void setFields(final List<SubmissionCCLicenseFieldRest> 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;
}
}

View File

@@ -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<String> {
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;
}
}

View File

@@ -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<ProcessFileTypesRest> {
public ProcessFileTypesResource(ProcessFileTypesRest content) {
super(content);
}
}

View File

@@ -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<SubmissionCCLicenseRest> {
public SubmissionCCLicenseResource(SubmissionCCLicenseRest submissionCCLicenseRest, Utils utils) {
super(submissionCCLicenseRest, utils);
}
}

View File

@@ -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<SubmissionCCLicenseUrlRest> {
public SubmissionCCLicenseUrlResource(SubmissionCCLicenseUrlRest submissionCCLicenseUrlRest, Utils utils) {
super(submissionCCLicenseUrlRest, utils);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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<Bitstrea
}
@Override
@PreAuthorize("hasPermission(#id, 'BITSTREAM', 'READ')")
@PreAuthorize("hasPermission(#id, 'BITSTREAM', 'METADATA_READ')")
public BitstreamRest findOne(Context context, UUID id) {
Bitstream bit = null;
try {
@@ -95,22 +91,7 @@ public class BitstreamRestRepository extends DSpaceObjectRestRepository<Bitstrea
@Override
@PreAuthorize("hasAuthority('ADMIN')")
public Page<BitstreamRest> findAll(Context context, Pageable pageable) {
List<Bitstream> bit = new ArrayList<Bitstream>();
Iterator<Bitstream> 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<BitstreamRest> page = new PageImpl<>(bit, pageable, total)
.map((bitstream) -> converter.toRest(bitstream, projection));
return page;
throw new RepositoryMethodNotImplementedException(BitstreamRest.NAME, "findAll");
}
@Override

View File

@@ -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<String> fileTypes = processService.getFileTypesForProcessBitstreams(context, process);
ProcessFileTypesRest processFileTypesRest = new ProcessFileTypesRest();
processFileTypesRest.setId("filetypes-" + processId);
processFileTypesRest.setValues(fileTypes);
return processFileTypesRest;
}
}

View File

@@ -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<BitstreamRest> getFilesFromProcess(@Nullable HttpServletRequest request,
Integer processId,
@Nullable Pageable optionalPageable,
Projection projection) throws SQLException, AuthorizeException {
List<BitstreamRest> 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);
}
}

View File

@@ -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<ProcessRest, Int
@Autowired
private ProcessService processService;
@Autowired
private ConverterService converterService;
@Autowired
private AuthorizeService authorizeService;
@Override
@PreAuthorize("hasPermission(#id, 'PROCESS', 'READ')")
public ProcessRest findOne(Context context, Integer id) {
@@ -60,6 +78,63 @@ public class ProcessRestRepository extends DSpaceRestRepository<ProcessRest, Int
}
}
/**
* Calls on the getBitstreams method to retrieve all the Bitstreams of this process
* @param processId The processId of the Process to retrieve the Bitstreams for
* @return The list of Bitstreams of the given Process
* @throws SQLException If something goes wrong
* @throws AuthorizeException If something goes wrong
*/
public List<BitstreamRest> getProcessBitstreams(Integer processId) throws SQLException, AuthorizeException {
Context context = obtainContext();
Process process = getProcess(processId, context);
List<Bitstream> 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<ProcessRest> getDomainClass() {
return ProcessRest.class;

View File

@@ -22,6 +22,7 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.app.rest.converter.DSpaceRunnableParameterConverter;
import org.dspace.app.rest.exception.DSpaceBadRequestException;
import org.dspace.app.rest.exception.UnprocessableEntityException;
import org.dspace.app.rest.model.ParameterValueRest;
import org.dspace.app.rest.model.ProcessRest;
import org.dspace.app.rest.model.ScriptRest;
@@ -30,6 +31,7 @@ import org.dspace.authorize.AuthorizeException;
import org.dspace.core.Context;
import org.dspace.scripts.DSpaceCommandLineParameter;
import org.dspace.scripts.DSpaceRunnable;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.dspace.scripts.service.ScriptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
@@ -37,6 +39,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
/**
* This is the REST repository dealing with the Script logic
@@ -56,10 +59,10 @@ public class ScriptRestRepository extends DSpaceRestRepository<ScriptRest, Strin
@PreAuthorize("permitAll()")
public ScriptRest findOne(Context context, String name) {
DSpaceRunnable dSpaceRunnable = scriptService.getScriptForName(name);
if (dSpaceRunnable != null) {
if (dSpaceRunnable.isAllowedToExecute(context)) {
return converter.toRest(dSpaceRunnable, utils.obtainProjection());
ScriptConfiguration scriptConfiguration = scriptService.getScriptConfiguration(name);
if (scriptConfiguration != null) {
if (scriptConfiguration.isAllowedToExecute(context)) {
return converter.toRest(scriptConfiguration, utils.obtainProjection());
} else {
throw new AccessDeniedException("The current user was not authorized to access this script");
}
@@ -69,8 +72,8 @@ public class ScriptRestRepository extends DSpaceRestRepository<ScriptRest, Strin
@Override
public Page<ScriptRest> findAll(Context context, Pageable pageable) {
List<DSpaceRunnable> dSpaceRunnables = scriptService.getDSpaceRunnables(context);
return converter.toRestPage(dSpaceRunnables, pageable, utils.obtainProjection());
List<ScriptConfiguration> scriptConfigurations = scriptService.getScriptConfigurations(context);
return converter.toRestPage(scriptConfigurations, pageable, utils.obtainProjection());
}
@Override
@@ -86,12 +89,12 @@ public class ScriptRestRepository extends DSpaceRestRepository<ScriptRest, Strin
* @throws SQLException If something goes wrong
* @throws IOException If something goes wrong
*/
public ProcessRest startProcess(Context context, String scriptName) throws SQLException,
IOException, AuthorizeException {
public ProcessRest startProcess(Context context, String scriptName, List<MultipartFile> files) throws SQLException,
IOException, AuthorizeException, IllegalAccessException, InstantiationException {
String properties = requestService.getCurrentRequest().getServletRequest().getParameter("properties");
List<DSpaceCommandLineParameter> 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");
}
@@ -101,7 +104,7 @@ public class ScriptRestRepository extends DSpaceRestRepository<ScriptRest, Strin
RestDSpaceRunnableHandler restDSpaceRunnableHandler = new RestDSpaceRunnableHandler(
context.getCurrentUser(), scriptName, dSpaceCommandLineParameters);
List<String> args = constructArgs(dSpaceCommandLineParameters);
runDSpaceScript(scriptToExecute, restDSpaceRunnableHandler, args);
runDSpaceScript(files, context, scriptToExecute, restDSpaceRunnableHandler, args);
return converter.toRest(restDSpaceRunnableHandler.getProcess(context), utils.obtainProjection());
}
@@ -131,13 +134,17 @@ public class ScriptRestRepository extends DSpaceRestRepository<ScriptRest, Strin
return args;
}
private void runDSpaceScript(DSpaceRunnable scriptToExecute,
RestDSpaceRunnableHandler restDSpaceRunnableHandler, List<String> args) {
private void runDSpaceScript(List<MultipartFile> files, Context context, ScriptConfiguration scriptToExecute,
RestDSpaceRunnableHandler restDSpaceRunnableHandler, List<String> 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()
@@ -145,4 +152,37 @@ public class ScriptRestRepository extends DSpaceRestRepository<ScriptRest, Strin
}
}
private void processFiles(Context context, RestDSpaceRunnableHandler restDSpaceRunnableHandler,
List<MultipartFile> 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<MultipartFile> files) {
List<String> 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<String> fileNamesFromOptions = dSpaceRunnable.getFileNamesFromInputStreamOptions();
if (!fileNames.containsAll(fileNamesFromOptions)) {
throw new UnprocessableEntityException("Files given in properties aren't all present in the request");
}
}
}

View File

@@ -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<SubmissionCCLicenseRest, String> {
@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<SubmissionCCLicenseRest> findAll(final Context context, final Pageable pageable) {
List<CCLicense> allCCLicenses = creativeCommonsService.findAllCCLicenses();
return converter.toRestPage(allCCLicenses, pageable, utils.obtainProjection());
}
@Override
public Class<SubmissionCCLicenseRest> getDomainClass() {
return SubmissionCCLicenseRest.class;
}
}

View File

@@ -292,7 +292,8 @@ public class WorkflowItemRestRepository extends DSpaceRestRepository<WorkflowIte
AbstractRestProcessingStep stepProcessing =
(AbstractRestProcessingStep) stepClass.newInstance();
stepProcessing.doPreProcessing(context, source);
stepProcessing.doPatchProcessing(context, getRequestService().getCurrentRequest(), source, op);
stepProcessing.doPatchProcessing(context, getRequestService().getCurrentRequest(),
source, op, stepConfig);
stepProcessing.doPostProcessing(context, source);
} else {
throw new DSpaceBadRequestException(

View File

@@ -292,6 +292,7 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository<WorkspaceI
private void evaluatePatch(Context context, HttpServletRequest request, WorkspaceItem source, WorkspaceItemRest wsi,
String section, Operation op) {
boolean sectionExist = false;
SubmissionConfig submissionConfig = submissionConfigReader
.getSubmissionConfigByName(wsi.getSubmissionDefinition().getName());
for (int stepNum = 0; stepNum < submissionConfig.getNumberOfSteps(); stepNum++) {
@@ -299,6 +300,7 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository<WorkspaceI
SubmissionStepConfig stepConfig = submissionConfig.getStep(stepNum);
if (section.equals(stepConfig.getId())) {
sectionExist = true;
/*
* First, load the step processing class (using the current
* class loader)
@@ -315,7 +317,8 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository<WorkspaceI
AbstractRestProcessingStep stepProcessing =
(AbstractRestProcessingStep) stepClass.newInstance();
stepProcessing.doPreProcessing(context, source);
stepProcessing.doPatchProcessing(context, getRequestService().getCurrentRequest(), source, op);
stepProcessing.doPatchProcessing(context,
getRequestService().getCurrentRequest(), source, op, stepConfig);
stepProcessing.doPostProcessing(context, source);
} else {
throw new DSpaceBadRequestException(
@@ -324,12 +327,18 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository<WorkspaceI
" Therefore it cannot be used by the Configurable Submission as the <processing-class>!");
}
} catch (UnprocessableEntityException e) {
throw e;
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new PatchException("Error processing the patch request", e);
}
}
}
if (!sectionExist) {
throw new UnprocessableEntityException("The section with name " + section +
" does not exist in this submission!");
}
}
@PreAuthorize("hasPermission(#id, 'WORKSPACEITEM', 'DELETE')")

View File

@@ -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,6 +186,31 @@ public class RestDSpaceRunnableHandler implements DSpaceRunnableHandler {
}
}
@Override
public Optional<InputStream> 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
@@ -196,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);
@@ -209,6 +247,6 @@ public class RestDSpaceRunnableHandler implements DSpaceRunnableHandler {
context.abort();
}
}
script.run();
taskExecutor.execute(script);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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";
@@ -94,7 +95,7 @@ public interface AbstractRestProcessingStep extends ListenerProcessingStep {
* the json patch operation
* @throws Exception
*/
public void doPatchProcessing(Context context, Request currentRequest, InProgressSubmission source, Operation op)
throws Exception;
public void doPatchProcessing(Context context, Request currentRequest, InProgressSubmission source, Operation op,
SubmissionStepConfig stepConf) throws Exception;
}

View File

@@ -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<XmlWorkflowItem> 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;
}
}

View File

@@ -11,6 +11,7 @@ import java.util.List;
import org.dspace.app.rest.model.MetadataValueRest;
import org.dspace.app.rest.model.patch.LateObjectEvaluator;
import org.dspace.app.rest.utils.BitstreamMetadataValuePathUtils;
import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.InProgressSubmission;
@@ -39,12 +40,17 @@ public class BitstreamMetadataValueAddPatchOperation extends MetadataValueAddPat
@Autowired
ItemService itemService;
// this is wired in the pring-dspace-core-services.xml
BitstreamMetadataValuePathUtils bitstreamMetadataValuePathUtils;
@Override
void add(Context context, Request currentRequest, InProgressSubmission source, String path, Object value)
throws Exception {
//"path": "/sections/upload/files/0/metadata/dc.title/2"
//"abspath": "/files/0/metadata/dc.title/2"
String[] split = getAbsolutePath(path).split("/");
String absolutePath = getAbsolutePath(path);
String[] split = absolutePath.split("/");
bitstreamMetadataValuePathUtils.validate(absolutePath);
Item item = source.getItem();
List<Bundle> bundle = itemService.getBundles(item, Constants.CONTENT_BUNDLE_NAME);
;
@@ -97,4 +103,8 @@ public class BitstreamMetadataValueAddPatchOperation extends MetadataValueAddPat
protected BitstreamService getDSpaceObjectService() {
return bitstreamService;
}
public void setBitstreamMetadataValuePathUtils(BitstreamMetadataValuePathUtils bitstreamMetadataValuePathUtils) {
this.bitstreamMetadataValuePathUtils = bitstreamMetadataValuePathUtils;
}
}

View File

@@ -9,6 +9,7 @@ package org.dspace.app.rest.submit.factory.impl;
import java.util.List;
import org.dspace.app.rest.utils.BitstreamMetadataValuePathUtils;
import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.InProgressSubmission;
@@ -35,12 +36,17 @@ public class BitstreamMetadataValueMovePatchOperation extends MetadataValueMoveP
@Autowired
ItemService itemService;
// this is wired in the pring-dspace-core-services.xml
BitstreamMetadataValuePathUtils bitstreamMetadataValuePathUtils;
@Override
void move(Context context, Request currentRequest, InProgressSubmission source, String path, String from)
throws Exception {
//"path": "/sections/upload/files/0/metadata/dc.title/2"
//"abspath": "/files/0/metadata/dc.title/2"
String[] splitTo = getAbsolutePath(path).split("/");
String absolutePath = getAbsolutePath(path);
String[] splitTo = absolutePath.split("/");
bitstreamMetadataValuePathUtils.validate(absolutePath);
Item item = source.getItem();
List<Bundle> bundle = itemService.getBundles(item, Constants.CONTENT_BUNDLE_NAME);
for (Bundle bb : bundle) {
@@ -72,4 +78,7 @@ public class BitstreamMetadataValueMovePatchOperation extends MetadataValueMoveP
return bitstreamService;
}
public void setBitstreamMetadataValuePathUtils(BitstreamMetadataValuePathUtils bitstreamMetadataValuePathUtils) {
this.bitstreamMetadataValuePathUtils = bitstreamMetadataValuePathUtils;
}
}

View File

@@ -9,6 +9,7 @@ package org.dspace.app.rest.submit.factory.impl;
import java.util.List;
import org.dspace.app.rest.utils.BitstreamMetadataValuePathUtils;
import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.InProgressSubmission;
@@ -35,12 +36,17 @@ public class BitstreamMetadataValueRemovePatchOperation extends MetadataValueRem
@Autowired
ItemService itemService;
// this is wired in the pring-dspace-core-services.xml
BitstreamMetadataValuePathUtils bitstreamMetadataValuePathUtils;
@Override
void remove(Context context, Request currentRequest, InProgressSubmission source, String path, Object value)
throws Exception {
//"path": "/sections/upload/files/0/metadata/dc.title/2"
//"abspath": "/files/0/metadata/dc.title/2"
String[] split = getAbsolutePath(path).split("/");
String absolutePath = getAbsolutePath(path);
String[] split = absolutePath.split("/");
bitstreamMetadataValuePathUtils.validate(absolutePath);
Item item = source.getItem();
List<Bundle> bundle = itemService.getBundles(item, Constants.CONTENT_BUNDLE_NAME);
;
@@ -67,4 +73,7 @@ public class BitstreamMetadataValueRemovePatchOperation extends MetadataValueRem
return bitstreamService;
}
public void setBitstreamMetadataValuePathUtils(BitstreamMetadataValuePathUtils bitstreamMetadataValuePathUtils) {
this.bitstreamMetadataValuePathUtils = bitstreamMetadataValuePathUtils;
}
}

View File

@@ -12,6 +12,7 @@ import java.util.List;
import org.dspace.app.rest.model.MetadataValueRest;
import org.dspace.app.rest.model.patch.LateObjectEvaluator;
import org.dspace.app.rest.utils.BitstreamMetadataValuePathUtils;
import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.InProgressSubmission;
@@ -38,12 +39,17 @@ public class BitstreamMetadataValueReplacePatchOperation extends MetadataValueRe
@Autowired
ItemService itemService;
// this is wired in the pring-dspace-core-services.xml
BitstreamMetadataValuePathUtils bitstreamMetadataValuePathUtils;
@Override
void replace(Context context, Request currentRequest, InProgressSubmission source, String path, Object value)
throws Exception {
//"path": "/sections/upload/files/0/metadata/dc.title/2"
//"abspath": "/files/0/metadata/dc.title/2"
String[] split = getAbsolutePath(path).split("/");
String absolutePath = getAbsolutePath(path);
String[] split = absolutePath.split("/");
bitstreamMetadataValuePathUtils.validate(absolutePath);
Item item = source.getItem();
List<Bundle> bundle = itemService.getBundles(item, Constants.CONTENT_BUNDLE_NAME);
for (Bundle bb : bundle) {
@@ -80,4 +86,8 @@ public class BitstreamMetadataValueReplacePatchOperation extends MetadataValueRe
protected BitstreamService getDSpaceObjectService() {
return bitstreamService;
}
public void setBitstreamMetadataValuePathUtils(BitstreamMetadataValuePathUtils bitstreamMetadataValuePathUtils) {
this.bitstreamMetadataValuePathUtils = bitstreamMetadataValuePathUtils;
}
}

View File

@@ -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: <code>
* 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/"}]'
* </code>
*/
public class CCLicenseAddPatchOperation extends AddPatchOperation<String> {
@Autowired
CreativeCommonsService creativeCommonsService;
@Override
protected Class<String[]> getArrayClassForEvaluation() {
return String[].class;
}
@Override
protected Class<String> 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");
}
}
}

View File

@@ -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: <code>
* curl -X PATCH http://${dspace.server.url}/api/submission/workspaceitems/31599 -H "Content-Type:
* application/json" -d '[{ "op": "remove", "path": "/sections/cclicense/uri"}]'
* </code>
*/
public class CCLicenseRemovePatchOperation extends RemovePatchOperation<String> {
@Autowired
CreativeCommonsService creativeCommonsService;
@Override
protected Class<String[]> getArrayClassForEvaluation() {
return String[].class;
}
@Override
protected Class<String> 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());
}
}
}

View File

@@ -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,
SubmissionStepConfig stepConf) throws Exception {
if (op.getPath().endsWith(CCLICENSE_STEP_OPERATION_ENTRY)) {
PatchOperation<String> patchOperation = new PatchOperationFactory()
.instanceOf(CCLICENSE_STEP_OPERATION_ENTRY, op.getOp());
patchOperation.perform(context, currentRequest, source, op);
}
}
}

View File

@@ -36,8 +36,8 @@ public class CollectionStep extends org.dspace.submit.step.SelectCollectionStep
}
@Override
public void doPatchProcessing(Context context, Request currentRequest, InProgressSubmission source, Operation op)
throws Exception {
public void doPatchProcessing(Context context, Request currentRequest, InProgressSubmission source, Operation op,
SubmissionStepConfig stepConf) throws Exception {
PatchOperation<String> patchOperation = new PatchOperationFactory()
.instanceOf(COLLECTION_STEP_OPERATION_ENTRY, op.getOp());

View File

@@ -10,9 +10,12 @@ package org.dspace.app.rest.submit.step;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.app.rest.exception.UnprocessableEntityException;
import org.dspace.app.rest.model.MetadataValueRest;
import org.dspace.app.rest.model.patch.Operation;
import org.dspace.app.rest.model.patch.RemoveOperation;
import org.dspace.app.rest.model.step.DataDescribe;
import org.dspace.app.rest.submit.AbstractRestProcessingStep;
import org.dspace.app.rest.submit.SubmissionService;
@@ -110,13 +113,56 @@ public class DescribeStep extends org.dspace.submit.step.DescribeStep implements
}
@Override
public void doPatchProcessing(Context context, Request currentRequest, InProgressSubmission source, Operation op)
throws Exception {
PatchOperation<MetadataValueRest> patchOperation = new PatchOperationFactory()
.instanceOf(DESCRIBE_STEP_METADATA_OPERATION_ENTRY, op.getOp());
patchOperation.perform(context, currentRequest, source, op);
public void doPatchProcessing(Context context, Request currentRequest, InProgressSubmission source, Operation op,
SubmissionStepConfig stepConf) throws Exception {
String[] pathParts = op.getPath().substring(1).split("/");
DCInputSet inputConfig = inputReader.getInputsByFormName(stepConf.getId());
if ("remove".equals(op.getOp()) && pathParts.length < 3) {
// manage delete all step fields
String[] path = op.getPath().substring(1).split("/", 3);
String configId = path[1];
List<String> fieldsName = getInputFieldsName(inputConfig, configId);
for (String fieldName : fieldsName) {
String fieldPath = op.getPath() + "/" + fieldName;
Operation fieldRemoveOp = new RemoveOperation(fieldPath);
PatchOperation<MetadataValueRest> patchOperation = new PatchOperationFactory()
.instanceOf(DESCRIBE_STEP_METADATA_OPERATION_ENTRY, fieldRemoveOp.getOp());
patchOperation.perform(context, currentRequest, source, fieldRemoveOp);
}
} else {
PatchOperation<MetadataValueRest> patchOperation = new PatchOperationFactory()
.instanceOf(DESCRIBE_STEP_METADATA_OPERATION_ENTRY, op.getOp());
String[] split = patchOperation.getAbsolutePath(op.getPath()).split("/");
if (inputConfig.isFieldPresent(split[0])) {
patchOperation.perform(context, currentRequest, source, op);
} else {
throw new UnprocessableEntityException("The field " + split[0] + " is not present in section "
+ inputConfig.getFormName());
}
}
}
private List<String> getInputFieldsName(DCInputSet inputConfig, String configId) throws DCInputsReaderException {
List<String> fieldsName = new ArrayList<String>();
for (DCInput[] row : inputConfig.getFields()) {
for (DCInput input : row) {
if (input.isQualdropValue()) {
for (Object qualifier : input.getPairs()) {
fieldsName.add(input.getFieldName() + "." + (String) qualifier);
}
} else if (StringUtils.equalsIgnoreCase(input.getInputType(), "group") ||
StringUtils.equalsIgnoreCase(input.getInputType(), "inline-group")) {
log.info("Called child form:" + configId + "-" +
Utils.standardize(input.getSchema(), input.getElement(), input.getQualifier(), "-"));
DCInputSet inputConfigChild = inputReader.getInputsByFormName(configId + "-" + Utils
.standardize(input.getSchema(), input.getElement(), input.getQualifier(), "-"));
fieldsName.addAll(getInputFieldsName(inputConfigChild, configId));
} else {
fieldsName.add(input.getFieldName());
}
}
}
return fieldsName;
}
}

View File

@@ -8,6 +8,7 @@
package org.dspace.app.rest.submit.step;
import org.atteo.evo.inflector.English;
import org.dspace.app.rest.exception.UnprocessableEntityException;
import org.dspace.app.rest.model.BitstreamRest;
import org.dspace.app.rest.model.patch.Operation;
import org.dspace.app.rest.model.step.DataLicense;
@@ -50,8 +51,8 @@ public class LicenseStep extends org.dspace.submit.step.LicenseStep implements A
}
@Override
public void doPatchProcessing(Context context, Request currentRequest, InProgressSubmission source, Operation op)
throws Exception {
public void doPatchProcessing(Context context, Request currentRequest, InProgressSubmission source, Operation op,
SubmissionStepConfig stepConf) throws Exception {
if (op.getPath().endsWith(LICENSE_STEP_OPERATION_ENTRY)) {
@@ -59,6 +60,8 @@ public class LicenseStep extends org.dspace.submit.step.LicenseStep implements A
.instanceOf(LICENSE_STEP_OPERATION_ENTRY, op.getOp());
patchOperation.perform(context, currentRequest, source, op);
} else {
throw new UnprocessableEntityException("The path " + op.getPath() + " cannot be patched");
}
}
}

View File

@@ -12,6 +12,7 @@ import java.io.InputStream;
import java.util.List;
import org.apache.logging.log4j.Logger;
import org.dspace.app.rest.exception.UnprocessableEntityException;
import org.dspace.app.rest.model.ErrorRest;
import org.dspace.app.rest.model.patch.Operation;
import org.dspace.app.rest.model.step.DataUpload;
@@ -45,6 +46,8 @@ public class UploadStep extends org.dspace.submit.step.UploadStep
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(UploadStep.class);
public static final String UPLOAD_STEP_METADATA_SECTION = "bitstream-metadata";
@Override
public DataUpload getData(SubmissionService submissionService, InProgressSubmission obj,
SubmissionStepConfig config) throws Exception {
@@ -61,10 +64,10 @@ public class UploadStep extends org.dspace.submit.step.UploadStep
}
@Override
public void doPatchProcessing(Context context, Request currentRequest, InProgressSubmission source, Operation op)
throws Exception {
public void doPatchProcessing(Context context, Request currentRequest, InProgressSubmission source, Operation op,
SubmissionStepConfig stepConf) throws Exception {
String instance = "";
String instance = null;
if ("remove".equals(op.getOp())) {
if (op.getPath().contains(UPLOAD_STEP_METADATA_PATH)) {
instance = UPLOAD_STEP_METADATA_OPERATION_ENTRY;
@@ -82,13 +85,16 @@ public class UploadStep extends org.dspace.submit.step.UploadStep
} else {
if (op.getPath().contains(UPLOAD_STEP_ACCESSCONDITIONS_OPERATION_ENTRY)) {
instance = UPLOAD_STEP_ACCESSCONDITIONS_OPERATION_ENTRY;
} else {
} else if (op.getPath().contains(UPLOAD_STEP_METADATA_PATH)) {
instance = UPLOAD_STEP_METADATA_OPERATION_ENTRY;
}
}
if (instance == null) {
throw new UnprocessableEntityException("The path " + op.getPath() + " is not supported by the operation "
+ op.getOp());
}
PatchOperation<?> patchOperation = new PatchOperationFactory().instanceOf(instance, op.getOp());
patchOperation.perform(context, currentRequest, source, op);
}
@Override

View File

@@ -0,0 +1,51 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.utils;
import org.dspace.app.rest.exception.UnprocessableEntityException;
import org.dspace.app.rest.submit.step.UploadStep;
import org.dspace.app.util.DCInputSet;
import org.dspace.app.util.DCInputsReader;
import org.dspace.app.util.DCInputsReaderException;
/**
* Utils class offering methods to validate patch operations for bitstream metadata in the submission
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.it)
*/
public class BitstreamMetadataValuePathUtils {
private DCInputsReader inputReader;
BitstreamMetadataValuePathUtils() throws DCInputsReaderException {
inputReader = new DCInputsReader();
}
/**
* Method to verify that the path included in the patch operation is supported
* by the submission configuration of the upload section
*
* @param absolutePath the path in the json patch operation
* @throws DCInputsReaderException if an error occurs reading the
* submission configuration
* @throws UnprocessableEntityException if the path is invalid
*/
public void validate(String absolutePath) throws DCInputsReaderException {
String[] split = absolutePath.split("/");
DCInputSet inputConfig = inputReader.getInputsByFormName(UploadStep.UPLOAD_STEP_METADATA_SECTION);
// according to the rest contract the absolute path must be something like files/:idx/metadata/dc.title
if (split.length >= 4) {
if (!inputConfig.isFieldPresent(split[3])) {
throw new UnprocessableEntityException("The field " + split[3] + " is not present in section "
+ UploadStep.UPLOAD_STEP_METADATA_SECTION);
}
} else {
throw new UnprocessableEntityException("The path " + absolutePath + " cannot be patched ");
}
}
}

View File

@@ -25,7 +25,9 @@
</entry>
<entry key="bitstreammetadata">
<bean
class="org.dspace.app.rest.submit.factory.impl.BitstreamMetadataValueMovePatchOperation"/>
class="org.dspace.app.rest.submit.factory.impl.BitstreamMetadataValueMovePatchOperation">
<property name="bitstreamMetadataValuePathUtils" ref="org.dspace.app.rest.utils.BitstreamMetadataValuePathUtils" />
</bean>
</entry>
<entry key="bitstreammove">
<bean
@@ -42,7 +44,9 @@
</entry>
<entry key="bitstreammetadata">
<bean
class="org.dspace.app.rest.submit.factory.impl.BitstreamMetadataValueAddPatchOperation"/>
class="org.dspace.app.rest.submit.factory.impl.BitstreamMetadataValueAddPatchOperation">
<property name="bitstreamMetadataValuePathUtils" ref="org.dspace.app.rest.utils.BitstreamMetadataValuePathUtils" />
</bean>
</entry>
<entry key="granted">
<bean
@@ -52,6 +56,10 @@
<bean
class="org.dspace.app.rest.submit.factory.impl.BitstreamResourcePolicyAddPatchOperation"/>
</entry>
<entry key="cclicense/uri">
<bean
class="org.dspace.app.rest.submit.factory.impl.CCLicenseAddPatchOperation"/>
</entry>
</map>
</entry>
<entry key="remove">
@@ -59,11 +67,13 @@
<!-- WARNING do not change "key" it match with Java code (TODO dynamic discover from PATCH operation); -->
<entry key="itemmetadata">
<bean
class="org.dspace.app.rest.submit.factory.impl.ItemMetadataValueRemovePatchOperation"/>
class="org.dspace.app.rest.submit.factory.impl.ItemMetadataValueRemovePatchOperation" />
</entry>
<entry key="bitstreammetadata">
<bean
class="org.dspace.app.rest.submit.factory.impl.BitstreamMetadataValueRemovePatchOperation"/>
class="org.dspace.app.rest.submit.factory.impl.BitstreamMetadataValueRemovePatchOperation">
<property name="bitstreamMetadataValuePathUtils" ref="org.dspace.app.rest.utils.BitstreamMetadataValuePathUtils" />
</bean>
</entry>
<entry key="granted">
<bean
@@ -71,12 +81,16 @@
</entry>
<entry key="bitstreamremove">
<bean
class="org.dspace.app.rest.submit.factory.impl.BitstreamRemovePatchOperation"/>
class="org.dspace.app.rest.submit.factory.impl.BitstreamRemovePatchOperation" />
</entry>
<entry key="accessConditions">
<bean
class="org.dspace.app.rest.submit.factory.impl.BitstreamResourcePolicyRemovePatchOperation"/>
</entry>
<entry key="cclicense/uri">
<bean
class="org.dspace.app.rest.submit.factory.impl.CCLicenseRemovePatchOperation"/>
</entry>
</map>
</entry>
<entry key="replace">
@@ -88,7 +102,9 @@
</entry>
<entry key="bitstreammetadata">
<bean
class="org.dspace.app.rest.submit.factory.impl.BitstreamMetadataValueReplacePatchOperation"/>
class="org.dspace.app.rest.submit.factory.impl.BitstreamMetadataValueReplacePatchOperation">
<property name="bitstreamMetadataValuePathUtils" ref="org.dspace.app.rest.utils.BitstreamMetadataValuePathUtils" />
</bean>
</entry>
<entry key="granted">
<bean
@@ -108,4 +124,5 @@
</property>
</bean>
<bean id="org.dspace.app.rest.utils.BitstreamMetadataValuePathUtils" class="org.dspace.app.rest.utils.BitstreamMetadataValuePathUtils"/>
</beans>

View File

@@ -0,0 +1,282 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE item-submission SYSTEM "item-submission.dtd">
<!-- Configurable Submission configuration file -->
<!-- This XML configuration file allows you to configure the ordering -->
<!-- and number of the steps that occur in the Item Submission Process. -->
<item-submission>
<!-- The process-map maps collection handles to a particular Item -->
<!-- Submission Process. This requires that a collection's name be -->
<!-- unique, even within a community. DSpace does however ensure that each -->
<!-- collection's handle is unique. Process-map provides the means to -->
<!-- associate a unique collection name with an Item Submission process. -->
<!-- The process-map also provides the special handle "default" (which is -->
<!-- never a collection), here mapped to "traditional". Any collection -->
<!-- which does not appear in this map will be associated with the mapping -->
<!-- for handle "default". -->
<submission-map>
<name-map collection-handle="default" submission-name="traditional"/>
</submission-map>
<!-- The 'step-definitions' allows you to define steps which you may wish -->
<!-- to "share" amongst multiple submission-item definitions. In order to -->
<!-- share the same step definition, you can refer to it by its unique id -->
<!-- defined in this section. EVERY 'step' in this section MUST have a -->
<!-- unique identifier in the 'id' attribute! -->
<!-- -->
<!-- Each <step> REQUIRES the following attributes (@) and properties: -->
<!-- @id - The unique identifier for this step -->
<!-- -->
<!-- <processing-class> - The class which will process all information for -->
<!-- this step. The class must extend -->
<!-- 'org.dspace.submit.AbstractProcessingStep' -->
<!-- (or one of the org.dspace.submit.step.* classes) -->
<!-- This property should reference the full path of the class -->
<!-- (e.g. org.dspace.submit.step.MyCustomStep) -->
<!-- -->
<!-- The following properties are OPTIONAL for each <step>: -->
<!-- <heading> - References the message key, from the -->
<!-- Messages.properties -->
<!-- -->
<step-definitions>
<!-- The "collection" step is a "special step" which is *REQUIRED* -->
<!-- In DSpace, all submitted items must be immediately assigned -->
<!-- to a collection. This step ensures that a collection is always selected. -->
<step id="collection">
<heading></heading>
<processing-class>org.dspace.app.rest.submit.step.CollectionStep</processing-class>
<type>collection</type>
<scope visibility="hidden" visibilityOutside="hidden">submission</scope>
</step>
<step id="traditionalpageone" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
</step>
<step id="traditionalpagetwo" mandatory="true">
<heading>submit.progressbar.describe.steptwo</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
</step>
<step id="peopleStep" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
</step>
<step id="projectStep" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
</step>
<step id="orgUnitStep" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
</step>
<step id="journalStep" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
</step>
<step id="journalVolumeStep" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
</step>
<step id="journalIssueStep" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
</step>
<step id="upload">
<heading>submit.progressbar.upload</heading>
<processing-class>org.dspace.app.rest.submit.step.UploadStep</processing-class>
<type>upload</type>
</step>
<step id="license">
<heading>submit.progressbar.license</heading>
<processing-class>org.dspace.app.rest.submit.step.LicenseStep</processing-class>
<type>license</type>
<scope visibilityOutside="read-only">submission</scope>
</step>
<!-- Step Upload Item with Embargo Features to enable this step, please
make sure to comment-out the previous step "UploadStep" <step id="upload-with-embargo">
<heading>submit.progressbar.upload</heading> <processing-class>org.dspace.submit.step.UploadWithEmbargoStep</processing-class>
<type>uploadWithEmbargo</type> </step> -->
<!--Step will be to select a Creative Commons License -->
<!-- Uncomment this step to allow the user to select a Creative Commons
license -->
<step id="cclicense"> <heading>submit.progressbar.CClicense</heading>
<processing-class>org.dspace.app.rest.submit.step.CCLicenseStep</processing-class>
<type>cclicense</type> </step>
<!--Step will be to Check for potential duplicate -->
<!-- <step id="detect-duplicate"> <heading>submit.progressbar.detect.duplicate</heading>
<processing-class>org.dspace.submit.step.DetectPotentialDuplicate</processing-class>
<type>duplicate</type> </step> -->
<!--Step will be to Verify/Review everything -->
<!-- <step id="verify"> <heading>submit.progressbar.verify</heading> <processing-class>org.dspace.submit.step.VerifyStep</processing-class>
<type>verify</type> </step> -->
<!-- Fake Steps to test parsing of all options -->
<!-- <step mandatory="false"> <heading>fake.submission.readonly</heading>
<processing-class>org.dspace.submit.step.SampleStep</processing-class> <type>sample</type>
<scope visibility="read-only">submission</scope> </step> <step mandatory="false">
<heading>fake.workflow.readonly</heading> <processing-class>org.dspace.submit.step.SampleStep</processing-class>
<type>sample</type> <scope visibility="read-only">workflow</scope> </step> -->
<!-- OpenAIRE submission steps/forms -->
<step id="openAIREProjectForm" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
</step>
<step id="openAIREPersonForm" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
</step>
<step id="openAIREOrganizationForm" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
</step>
<step id="openAIREPublicationPageoneForm" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
</step>
<step id="openAIREPublicationPagetwoForm" mandatory="true">
<heading>submit.progressbar.describe.stepone</heading>
<processing-class>org.dspace.app.rest.submit.step.DescribeStep</processing-class>
<type>submission-form</type>
</step>
<!-- This is the Sample Step which utilizes the JSPSampleStep class -->
<step id="sample">
<heading>Sample</heading>
<processing-class>org.dspace.submit.step.SampleStep</processing-class>
<type>sample</type>
</step>
</step-definitions>
<!-- The submission-definitions map lays out the detailed definition of -->
<!-- all the Item Submission Processes (and the ordering of their steps). -->
<!-- Each separate "submission-process" has a unique name as an attribute, -->
<!-- which matches one of the names in the process-map. One named -->
<!-- "submit-process" has the name "traditional"; as this name suggests, -->
<!-- it is the default item submission process, which gets used when -->
<!-- the specified collection has no correspondingly named submit-process. -->
<!-- -->
<!-- Each submit-process contains an ordered set of steps; each step -->
<!-- defines one "step" occurring during the process of submitting an -->
<!-- item. A step MUST be referenced by 'id' (it must -->
<!-- be defined in <step-definitions> above). -->
<!-- -->
<!-- -->
<submission-definitions>
<!--This "traditional" process defines the DEFAULT item submission process -->
<submission-process name="traditional">
<!--Uncommment to display the SAMPLE step as your first step -->
<!--<step id="sample"/> -->
<step id="collection"/>
<!--Step will be to Describe the item. -->
<step id="traditionalpageone"/>
<step id="traditionalpagetwo"/>
<!--Step will be to Upload the item -->
<step id="upload"/>
<!-- <step id="upload-with-embargo"/> -->
<!-- <step id="detect-duplicate"/> -->
<step id="cclicense"/>
<!--Step will be to Sign off on the License -->
<step id="license"/>
<!-- <step id="creative-commons"/> -->
<!-- <step id="verify"/> -->
</submission-process>
<submission-process name="People">
<step id="collection"/>
<step id="peopleStep"/>
<step id="upload"/>
<step id="license"/>
</submission-process>
<submission-process name="Project">
<step id="collection"/>
<step id="projectStep"/>
<step id="upload"/>
<step id="license"/>
</submission-process>
<submission-process name="OrgUnit">
<step id="collection"/>
<step id="orgUnitStep"/>
<step id="upload"/>
<step id="license"/>
</submission-process>
<submission-process name="Journals">
<step id="collection"/>
<step id="journalStep"/>
<step id="upload"/>
<step id="license"/>
</submission-process>
<submission-process name="JournalVolumes">
<step id="collection"/>
<step id="journalVolumeStep"/>
<step id="upload"/>
<step id="license"/>
</submission-process>
<submission-process name="JournalIssues">
<step id="collection"/>
<step id="journalIssueStep"/>
<step id="upload"/>
<step id="license"/>
</submission-process>
<!-- OpenAIRE submission processes -->
<submission-process name="openAIREPublicationSubmission">
<step id="collection"/>
<!--Step will be to Describe the item. -->
<step id="openAIREPublicationPageoneForm"/>
<step id="openAIREPublicationPagetwoForm"/>
<!--Step will be to Upload the item -->
<!-- step id="upload-with-embargo"/-->
<step id="upload"/>
<!--Step will be to Sign off on the License -->
<step id="license"/>
</submission-process>
<submission-process name="openAIREPersonSubmission">
<step id="collection"/>
<step id="openAIREPersonForm"/>
</submission-process>
<submission-process name="openAIREProjectSubmission">
<step id="collection"/>
<step id="openAIREProjectForm"/>
</submission-process>
<submission-process name="openAIREOrganizationSubmission">
<step id="collection"/>
<step id="openAIREOrganizationForm"/>
</submission-process>
</submission-definitions>
</item-submission>

View File

@@ -6,4 +6,5 @@
<!-- Replace harvestCollectionService with a mocked service -->
<bean class="org.dspace.harvest.MockHarvestedCollectionServiceImpl" id="org.dspace.harvest.service.HarvestedCollectionService" primary="true"/>
<bean class="org.dspace.license.MockCCLicenseConnectorServiceImpl" id="org.dspace.license.CCLicenseConnectorService" primary="true"/>
</beans>

View File

@@ -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 {

View File

@@ -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<Operation> ops = new ArrayList<Operation>();
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<Operation> ops = new ArrayList<Operation>();
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"))));
}
}

Some files were not shown because too many files have changed in this diff Show More