diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 9b7ed5971f..194d84b446 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -548,17 +548,11 @@ com.ibm.icu icu4j - + org.dspace oclc-harvester2 - - - xalan - xalan - org.dspace dspace-services diff --git a/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java b/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java index 80d69f3b66..81250e9c82 100644 --- a/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java +++ b/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java @@ -14,6 +14,7 @@ import java.util.Locale; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Options; import org.apache.commons.lang3.StringUtils; import org.dspace.core.Context; @@ -54,14 +55,14 @@ public final class CreateAdministrator { protected GroupService groupService; /** - * For invoking via the command line. If called with no command line arguments, + * For invoking via the command line. If called with no command line arguments, * it will negotiate with the user for the administrator details * * @param argv the command line arguments given * @throws Exception if error */ public static void main(String[] argv) - throws Exception { + throws Exception { CommandLineParser parser = new DefaultParser(); Options options = new Options(); @@ -69,19 +70,41 @@ public final class CreateAdministrator { options.addOption("e", "email", true, "administrator email address"); options.addOption("f", "first", true, "administrator first name"); + options.addOption("h", "help", false, "explain create-administrator options"); options.addOption("l", "last", true, "administrator last name"); options.addOption("c", "language", true, "administrator language"); options.addOption("p", "password", true, "administrator password"); - CommandLine line = parser.parse(options, argv); + CommandLine line = null; + + try { + + line = parser.parse(options, argv); + + } catch (Exception e) { + + System.out.println(e.getMessage() + "\nTry \"dspace create-administrator -h\" to print help information."); + System.exit(1); + + } if (line.hasOption("e") && line.hasOption("f") && line.hasOption("l") && - line.hasOption("c") && line.hasOption("p")) { + line.hasOption("c") && line.hasOption("p")) { ca.createAdministrator(line.getOptionValue("e"), - line.getOptionValue("f"), line.getOptionValue("l"), - line.getOptionValue("c"), line.getOptionValue("p")); + line.getOptionValue("f"), line.getOptionValue("l"), + line.getOptionValue("c"), line.getOptionValue("p")); + } else if (line.hasOption("h")) { + String header = "\nA command-line tool for creating an initial administrator for setting up a" + + " DSpace site. Unless all the required parameters are passed it will" + + " prompt for an e-mail address, last name, first name and password from" + + " standard input.. An administrator group is then created and the data passed" + + " in used to create an e-person in that group.\n\n"; + String footer = "\n"; + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("dspace create-administrator", header, options, footer, true); + return; } else { - ca.negotiateAdministratorDetails(); + ca.negotiateAdministratorDetails(line); } } @@ -91,7 +114,7 @@ public final class CreateAdministrator { * @throws Exception if error */ protected CreateAdministrator() - throws Exception { + throws Exception { context = new Context(); groupService = EPersonServiceFactory.getInstance().getGroupService(); ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); @@ -103,20 +126,20 @@ public final class CreateAdministrator { * * @throws Exception if error */ - protected void negotiateAdministratorDetails() - throws Exception { + protected void negotiateAdministratorDetails(CommandLine line) + throws Exception { Console console = System.console(); System.out.println("Creating an initial administrator account"); - boolean dataOK = false; - - String email = null; - String firstName = null; - String lastName = null; - char[] password1 = null; - char[] password2 = null; + String email = line.getOptionValue('e'); + String firstName = line.getOptionValue('f'); + String lastName = line.getOptionValue('l'); String language = I18nUtil.getDefaultLocale().getLanguage(); + ConfigurationService cfg = DSpaceServicesFactory.getInstance().getConfigurationService(); + boolean flag = line.hasOption('p'); + char[] password = null; + boolean dataOK = line.hasOption('f') && line.hasOption('e') && line.hasOption('l'); while (!dataOK) { System.out.print("E-mail address: "); @@ -147,8 +170,6 @@ public final class CreateAdministrator { if (lastName != null) { lastName = lastName.trim(); } - - ConfigurationService cfg = DSpaceServicesFactory.getInstance().getConfigurationService(); if (cfg.hasProperty("webui.supported.locales")) { System.out.println("Select one of the following languages: " + cfg.getProperty("webui.supported.locales")); @@ -163,46 +184,59 @@ public final class CreateAdministrator { } } - System.out.println("Password will not display on screen."); - System.out.print("Password: "); + System.out.print("Is the above data correct? (y or n): "); System.out.flush(); - password1 = console.readPassword(); + String s = console.readLine(); - System.out.print("Again to confirm: "); - System.out.flush(); - - password2 = console.readPassword(); - - //TODO real password validation - if (password1.length > 1 && Arrays.equals(password1, password2)) { - // password OK - System.out.print("Is the above data correct? (y or n): "); - System.out.flush(); - - String s = console.readLine(); - - if (s != null) { - s = s.trim(); - if (s.toLowerCase().startsWith("y")) { - dataOK = true; - } + if (s != null) { + s = s.trim(); + if (s.toLowerCase().startsWith("y")) { + dataOK = true; } - } else { - System.out.println("Passwords don't match"); } + + } + if (!flag) { + password = getPassword(console); + if (password == null) { + return; + } + } else { + password = line.getOptionValue("p").toCharArray(); } - // if we make it to here, we are ready to create an administrator - createAdministrator(email, firstName, lastName, language, String.valueOf(password1)); + createAdministrator(email, firstName, lastName, language, String.valueOf(password)); - //Cleaning arrays that held password - Arrays.fill(password1, ' '); - Arrays.fill(password2, ' '); + } + + private char[] getPassword(Console console) { + char[] password1 = null; + char[] password2 = null; + System.out.println("Password will not display on screen."); + System.out.print("Password: "); + System.out.flush(); + + password1 = console.readPassword(); + + System.out.print("Again to confirm: "); + System.out.flush(); + + password2 = console.readPassword(); + + // TODO real password validation + if (password1.length > 1 && Arrays.equals(password1, password2)) { + // password OK + Arrays.fill(password2, ' '); + return password1; + } else { + System.out.println("Passwords don't match"); + return null; + } } /** - * Create the administrator with the given details. If the user + * Create the administrator with the given details. If the user * already exists then they are simply upped to administrator status * * @param email the email for the user @@ -213,8 +247,8 @@ public final class CreateAdministrator { * @throws Exception if error */ protected void createAdministrator(String email, String first, String last, - String language, String pw) - throws Exception { + String language, String pw) + throws Exception { // Of course we aren't an administrator yet so we need to // circumvent authorisation context.turnOffAuthorisationSystem(); diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/CollectionAdministratorsRequestItemStrategy.java b/dspace-api/src/main/java/org/dspace/app/requestitem/CollectionAdministratorsRequestItemStrategy.java new file mode 100644 index 0000000000..135406069a --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/CollectionAdministratorsRequestItemStrategy.java @@ -0,0 +1,46 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ + +package org.dspace.app.requestitem; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.springframework.lang.NonNull; + +/** + * Derive request recipients from groups of the Collection which owns an Item. + * The list will include all members of the administrators group. If the + * resulting list is empty, delegates to {@link RequestItemHelpdeskStrategy}. + * + * @author Mark H. Wood + */ +public class CollectionAdministratorsRequestItemStrategy + extends RequestItemHelpdeskStrategy { + @Override + @NonNull + public List getRequestItemAuthor(Context context, + Item item) + throws SQLException { + List recipients = new ArrayList<>(); + Collection collection = item.getOwningCollection(); + for (EPerson admin : collection.getAdministrators().getMembers()) { + recipients.add(new RequestItemAuthor(admin)); + } + if (recipients.isEmpty()) { + return super.getRequestItemAuthor(context, item); + } else { + return recipients; + } + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/CombiningRequestItemStrategy.java b/dspace-api/src/main/java/org/dspace/app/requestitem/CombiningRequestItemStrategy.java new file mode 100644 index 0000000000..8292c1a728 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/CombiningRequestItemStrategy.java @@ -0,0 +1,61 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.requestitem; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.springframework.lang.NonNull; +import org.springframework.util.Assert; + +/** + * Assemble a list of recipients from the results of other strategies. + * The list of strategy classes is injected as the constructor argument + * {@code strategies}. + * If the strategy list is not configured, returns an empty List. + * + * @author Mark H. Wood + */ +public class CombiningRequestItemStrategy + implements RequestItemAuthorExtractor { + /** The strategies to combine. */ + private final List strategies; + + /** + * Initialize a combination of strategies. + * @param strategies the author extraction strategies to combine. + */ + public CombiningRequestItemStrategy(@NonNull List strategies) { + Assert.notNull(strategies, "Strategy list may not be null"); + this.strategies = strategies; + } + + /** + * Do not call. + * @throws IllegalArgumentException always + */ + private CombiningRequestItemStrategy() { + throw new IllegalArgumentException(); + } + + @Override + @NonNull + public List getRequestItemAuthor(Context context, Item item) + throws SQLException { + List recipients = new ArrayList<>(); + + for (RequestItemAuthorExtractor strategy : strategies) { + recipients.addAll(strategy.getRequestItemAuthor(context, item)); + } + + return recipients; + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItem.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItem.java index 9e675e97a7..cdefd1298c 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItem.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItem.java @@ -27,7 +27,7 @@ import org.dspace.core.Context; import org.dspace.core.ReloadableEntity; /** - * Object representing an Item Request + * Object representing an Item Request. */ @Entity @Table(name = "requestitem") @@ -94,6 +94,9 @@ public class RequestItem implements ReloadableEntity { this.allfiles = allfiles; } + /** + * @return {@code true} if all of the Item's files are requested. + */ public boolean isAllfiles() { return allfiles; } @@ -102,6 +105,9 @@ public class RequestItem implements ReloadableEntity { this.reqMessage = reqMessage; } + /** + * @return a message from the requester. + */ public String getReqMessage() { return reqMessage; } @@ -110,6 +116,9 @@ public class RequestItem implements ReloadableEntity { this.reqName = reqName; } + /** + * @return Human-readable name of the user requesting access. + */ public String getReqName() { return reqName; } @@ -118,6 +127,9 @@ public class RequestItem implements ReloadableEntity { this.reqEmail = reqEmail; } + /** + * @return address of the user requesting access. + */ public String getReqEmail() { return reqEmail; } @@ -126,6 +138,9 @@ public class RequestItem implements ReloadableEntity { this.token = token; } + /** + * @return a unique request identifier which can be emailed. + */ public String getToken() { return token; } diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthor.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthor.java index 49e26fe00b..a189e4a5ef 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthor.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthor.java @@ -11,20 +11,31 @@ import org.dspace.eperson.EPerson; /** * Simple DTO to transfer data about the corresponding author for the Request - * Copy feature + * Copy feature. * * @author Andrea Bollini */ public class RequestItemAuthor { - private String fullName; - private String email; + private final String fullName; + private final String email; + /** + * Construct an author record from given data. + * + * @param fullName the author's full name. + * @param email the author's email address. + */ public RequestItemAuthor(String fullName, String email) { super(); this.fullName = fullName; this.email = email; } + /** + * Construct an author from an EPerson's metadata. + * + * @param ePerson the EPerson. + */ public RequestItemAuthor(EPerson ePerson) { super(); this.fullName = ePerson.getFullName(); diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthorExtractor.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthorExtractor.java index 9b66030e90..bc97bc64bf 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthorExtractor.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemAuthorExtractor.java @@ -8,26 +8,28 @@ package org.dspace.app.requestitem; import java.sql.SQLException; +import java.util.List; import org.dspace.content.Item; import org.dspace.core.Context; +import org.springframework.lang.NonNull; /** - * Interface to abstract the strategy for select the author to contact for - * request copy + * Interface to abstract the strategy for selecting the author to contact for + * request copy. * * @author Andrea Bollini */ public interface RequestItemAuthorExtractor { - /** - * Retrieve the auhtor to contact for a request copy of the give item. + * Retrieve the author to contact for requesting a copy of the given item. * * @param context DSpace context object * @param item item to request - * @return An object containing name an email address to send the request to - * or null if no valid email address was found. + * @return Names and email addresses to send the request to. * @throws SQLException if database error */ - public RequestItemAuthor getRequestItemAuthor(Context context, Item item) throws SQLException; + @NonNull + public List getRequestItemAuthor(Context context, Item item) + throws SQLException; } diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java index d72e42eac1..02054ee1a0 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java @@ -72,28 +72,48 @@ public class RequestItemEmailNotifier { static public void sendRequest(Context context, RequestItem ri, String responseLink) throws IOException, SQLException { // Who is making this request? - RequestItemAuthor author = requestItemAuthorExtractor + List authors = requestItemAuthorExtractor .getRequestItemAuthor(context, ri.getItem()); - String authorEmail = author.getEmail(); - String authorName = author.getFullName(); // Build an email to the approver. Email email = Email.getEmail(I18nUtil.getEmailFilename(context.getCurrentLocale(), "request_item.author")); - email.addRecipient(authorEmail); + for (RequestItemAuthor author : authors) { + email.addRecipient(author.getEmail()); + } email.setReplyTo(ri.getReqEmail()); // Requester's address + email.addArgument(ri.getReqName()); // {0} Requester's name + email.addArgument(ri.getReqEmail()); // {1} Requester's address + email.addArgument(ri.isAllfiles() // {2} All bitstreams or just one? ? I18nUtil.getMessage("itemRequest.all") : ri.getBitstream().getName()); - email.addArgument(handleService.getCanonicalForm(ri.getItem().getHandle())); + + email.addArgument(handleService.getCanonicalForm(ri.getItem().getHandle())); // {3} + email.addArgument(ri.getItem().getName()); // {4} requested item's title + email.addArgument(ri.getReqMessage()); // {5} message from requester + email.addArgument(responseLink); // {6} Link back to DSpace for action - email.addArgument(authorName); // {7} corresponding author name - email.addArgument(authorEmail); // {8} corresponding author email - email.addArgument(configurationService.getProperty("dspace.name")); - email.addArgument(configurationService.getProperty("mail.helpdesk")); + + StringBuilder names = new StringBuilder(); + StringBuilder addresses = new StringBuilder(); + for (RequestItemAuthor author : authors) { + if (names.length() > 0) { + names.append("; "); + addresses.append("; "); + } + names.append(author.getFullName()); + addresses.append(author.getEmail()); + } + email.addArgument(names.toString()); // {7} corresponding author name + email.addArgument(addresses.toString()); // {8} corresponding author email + + email.addArgument(configurationService.getProperty("dspace.name")); // {9} + + email.addArgument(configurationService.getProperty("mail.helpdesk")); // {10} // Send the email. try { diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategy.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategy.java index 7b63d3ea8d..f440ba380a 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategy.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemHelpdeskStrategy.java @@ -8,6 +8,8 @@ package org.dspace.app.requestitem; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; import org.apache.commons.lang3.StringUtils; import org.dspace.content.Item; @@ -16,11 +18,11 @@ import org.dspace.core.I18nUtil; import org.dspace.eperson.EPerson; import org.dspace.eperson.service.EPersonService; import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.lang.NonNull; /** - * RequestItem strategy to allow DSpace support team's helpdesk to receive requestItem request + * RequestItem strategy to allow DSpace support team's helpdesk to receive requestItem request. * With this enabled, then the Item author/submitter doesn't receive the request, but the helpdesk instead does. * * Failover to the RequestItemSubmitterStrategy, which means the submitter would get the request if there is no @@ -33,19 +35,24 @@ public class RequestItemHelpdeskStrategy extends RequestItemSubmitterStrategy { @Autowired(required = true) protected EPersonService ePersonService; + @Autowired(required = true) + private ConfigurationService configuration; + public RequestItemHelpdeskStrategy() { } @Override - public RequestItemAuthor getRequestItemAuthor(Context context, Item item) throws SQLException { - ConfigurationService configurationService - = DSpaceServicesFactory.getInstance().getConfigurationService(); - boolean helpdeskOverridesSubmitter = configurationService + @NonNull + public List getRequestItemAuthor(Context context, Item item) + throws SQLException { + boolean helpdeskOverridesSubmitter = configuration .getBooleanProperty("request.item.helpdesk.override", false); - String helpDeskEmail = configurationService.getProperty("mail.helpdesk"); + String helpDeskEmail = configuration.getProperty("mail.helpdesk"); if (helpdeskOverridesSubmitter && StringUtils.isNotBlank(helpDeskEmail)) { - return getHelpDeskPerson(context, helpDeskEmail); + List authors = new ArrayList<>(1); + authors.add(getHelpDeskPerson(context, helpDeskEmail)); + return authors; } else { //Fallback to default logic (author of Item) if helpdesk isn't fully enabled or setup return super.getRequestItemAuthor(context, item); diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemMetadataStrategy.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemMetadataStrategy.java index 1737490fbb..4372ab9b09 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemMetadataStrategy.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemMetadataStrategy.java @@ -8,6 +8,8 @@ package org.dspace.app.requestitem; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.apache.commons.lang3.StringUtils; @@ -16,12 +18,13 @@ import org.dspace.content.MetadataValue; import org.dspace.content.service.ItemService; import org.dspace.core.Context; import org.dspace.core.I18nUtil; -import org.dspace.services.factory.DSpaceServicesFactory; +import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.lang.NonNull; /** * Try to look to an item metadata for the corresponding author name and email. - * Failover to the RequestItemSubmitterStrategy + * Failover to the RequestItemSubmitterStrategy. * * @author Andrea Bollini */ @@ -30,6 +33,9 @@ public class RequestItemMetadataStrategy extends RequestItemSubmitterStrategy { protected String emailMetadata; protected String fullNameMetadata; + @Autowired(required = true) + protected ConfigurationService configurationService; + @Autowired(required = true) protected ItemService itemService; @@ -37,59 +43,72 @@ public class RequestItemMetadataStrategy extends RequestItemSubmitterStrategy { } @Override - public RequestItemAuthor getRequestItemAuthor(Context context, Item item) + @NonNull + public List getRequestItemAuthor(Context context, Item item) throws SQLException { - RequestItemAuthor author = null; + List authors; if (emailMetadata != null) { List vals = itemService.getMetadataByMetadataString(item, emailMetadata); - if (vals.size() > 0) { - String email = vals.iterator().next().getValue(); - String fullname = null; - if (fullNameMetadata != null) { - List nameVals = itemService.getMetadataByMetadataString(item, fullNameMetadata); - if (nameVals.size() > 0) { - fullname = nameVals.iterator().next().getValue(); + List nameVals; + if (null != fullNameMetadata) { + nameVals = itemService.getMetadataByMetadataString(item, fullNameMetadata); + } else { + nameVals = Collections.EMPTY_LIST; + } + boolean useNames = vals.size() == nameVals.size(); + if (!vals.isEmpty()) { + authors = new ArrayList<>(vals.size()); + for (int authorIndex = 0; authorIndex < vals.size(); authorIndex++) { + String email = vals.get(authorIndex).getValue(); + String fullname = null; + if (useNames) { + fullname = nameVals.get(authorIndex).getValue(); } + + if (StringUtils.isBlank(fullname)) { + fullname = I18nUtil.getMessage( + "org.dspace.app.requestitem.RequestItemMetadataStrategy.unnamed", + context); + } + RequestItemAuthor author = new RequestItemAuthor( + fullname, email); + authors.add(author); } - if (StringUtils.isBlank(fullname)) { - fullname = I18nUtil - .getMessage( - "org.dspace.app.requestitem.RequestItemMetadataStrategy.unnamed", - context); - } - author = new RequestItemAuthor(fullname, email); - return author; + return authors; + } else { + return Collections.EMPTY_LIST; } } else { // Uses the basic strategy to look for the original submitter - author = super.getRequestItemAuthor(context, item); - // Is the author or their email null. If so get the help desk or admin name and email - if (null == author || null == author.getEmail()) { - String email = null; - String name = null; + authors = super.getRequestItemAuthor(context, item); + + // Remove from the list authors that do not have email addresses. + for (RequestItemAuthor author : authors) { + if (null == author.getEmail()) { + authors.remove(author); + } + } + + if (authors.isEmpty()) { // No author email addresses! Fall back //First get help desk name and email - email = DSpaceServicesFactory.getInstance() - .getConfigurationService().getProperty("mail.helpdesk"); - name = DSpaceServicesFactory.getInstance() - .getConfigurationService().getProperty("mail.helpdesk.name"); + String email = configurationService.getProperty("mail.helpdesk"); + String name = configurationService.getProperty("mail.helpdesk.name"); // If help desk mail is null get the mail and name of admin if (email == null) { - email = DSpaceServicesFactory.getInstance() - .getConfigurationService().getProperty("mail.admin"); - name = DSpaceServicesFactory.getInstance() - .getConfigurationService().getProperty("mail.admin.name"); + email = configurationService.getProperty("mail.admin"); + name = configurationService.getProperty("mail.admin.name"); } - author = new RequestItemAuthor(name, email); + authors.add(new RequestItemAuthor(name, email)); } + return authors; } - return author; } - public void setEmailMetadata(String emailMetadata) { + public void setEmailMetadata(@NonNull String emailMetadata) { this.emailMetadata = emailMetadata; } - public void setFullNameMetadata(String fullNameMetadata) { + public void setFullNameMetadata(@NonNull String fullNameMetadata) { this.fullNameMetadata = fullNameMetadata; } diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java index 2708c24ba9..dcc1a3e80e 100644 --- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java +++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemSubmitterStrategy.java @@ -8,10 +8,13 @@ package org.dspace.app.requestitem; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.eperson.EPerson; +import org.springframework.lang.NonNull; /** * Basic strategy that looks to the original submitter. @@ -24,21 +27,23 @@ public class RequestItemSubmitterStrategy implements RequestItemAuthorExtractor } /** - * Returns the submitter of an Item as RequestItemAuthor or null if the - * Submitter is deleted. + * Returns the submitter of an Item as RequestItemAuthor or an empty List if + * the Submitter is deleted. * - * @return The submitter of the item or null if the submitter is deleted + * @return The submitter of the item or empty List if the submitter is deleted * @throws SQLException if database error */ @Override - public RequestItemAuthor getRequestItemAuthor(Context context, Item item) + @NonNull + public List getRequestItemAuthor(Context context, Item item) throws SQLException { EPerson submitter = item.getSubmitter(); - RequestItemAuthor author = null; + List authors = new ArrayList<>(1); if (null != submitter) { - author = new RequestItemAuthor( - submitter.getFullName(), submitter.getEmail()); + RequestItemAuthor author = new RequestItemAuthor( + submitter.getFullName(), submitter.getEmail()); + authors.add(author); } - return author; + return authors; } } diff --git a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java index 0b76186e48..685fd9000d 100644 --- a/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java +++ b/dspace-api/src/main/java/org/dspace/content/packager/AbstractMETSDisseminator.java @@ -14,9 +14,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.UnsupportedEncodingException; import java.lang.reflect.InvocationTargetException; -import java.net.URLEncoder; import java.sql.SQLException; import java.util.HashMap; import java.util.Iterator; @@ -465,17 +463,17 @@ public abstract class AbstractMETSDisseminator Utils.copy(input, zip); input.close(); } else { - log.warn("Adding zero-length file for Bitstream, SID=" - + String.valueOf(bitstream.getSequenceID()) + log.warn("Adding zero-length file for Bitstream, uuid=" + + String.valueOf(bitstream.getID()) + ", not authorized for READ."); } zip.closeEntry(); } else if (unauth != null && unauth.equalsIgnoreCase("skip")) { - log.warn("Skipping Bitstream, SID=" + String - .valueOf(bitstream.getSequenceID()) + ", not authorized for READ."); + log.warn("Skipping Bitstream, uuid=" + String + .valueOf(bitstream.getID()) + ", not authorized for READ."); } else { throw new AuthorizeException( - "Not authorized to read Bitstream, SID=" + String.valueOf(bitstream.getSequenceID())); + "Not authorized to read Bitstream, uuid=" + String.valueOf(bitstream.getID())); } } } @@ -896,12 +894,12 @@ public abstract class AbstractMETSDisseminator continue; } else if (!(unauth != null && unauth.equalsIgnoreCase("zero"))) { throw new AuthorizeException( - "Not authorized to read Bitstream, SID=" + String.valueOf(bitstream.getSequenceID())); + "Not authorized to read Bitstream, uuid=" + String.valueOf(bitstream.getID())); } } - String sid = String.valueOf(bitstream.getSequenceID()); - String fileID = bitstreamIDstart + sid; + String uuid = String.valueOf(bitstream.getID()); + String fileID = bitstreamIDstart + uuid; edu.harvard.hul.ois.mets.File file = new edu.harvard.hul.ois.mets.File(); file.setID(fileID); file.setSEQ(bitstream.getSequenceID()); @@ -924,7 +922,7 @@ public abstract class AbstractMETSDisseminator * extracted text or a thumbnail, so we use the name to work * out which bitstream to be in the same group as */ - String groupID = "GROUP_" + bitstreamIDstart + sid; + String groupID = "GROUP_" + bitstreamIDstart + uuid; if ((bundle.getName() != null) && (bundle.getName().equals("THUMBNAIL") || bundle.getName().startsWith("TEXT"))) { @@ -934,7 +932,7 @@ public abstract class AbstractMETSDisseminator bitstream); if (original != null) { groupID = "GROUP_" + bitstreamIDstart - + original.getSequenceID(); + + String.valueOf(original.getID()); } } file.setGROUPID(groupID); @@ -1403,7 +1401,7 @@ public abstract class AbstractMETSDisseminator // if bare manifest, use external "persistent" URI for bitstreams if (params != null && (params.getBooleanProperty("manifestOnly", false))) { // Try to build a persistent(-ish) URI for bitstream - // Format: {site-base-url}/bitstream/{item-handle}/{sequence-id}/{bitstream-name} + // Format: {site-ui-url}/bitstreams/{bitstream-uuid} try { // get handle of parent Item of this bitstream, if there is one: String handle = null; @@ -1414,26 +1412,13 @@ public abstract class AbstractMETSDisseminator handle = bi.get(0).getHandle(); } } - if (handle != null) { - return configurationService - .getProperty("dspace.ui.url") - + "/bitstream/" - + handle - + "/" - + String.valueOf(bitstream.getSequenceID()) - + "/" - + URLEncoder.encode(bitstream.getName(), "UTF-8"); - } else { //no Handle assigned, so persistent(-ish) URI for bitstream is - // Format: {site-base-url}/retrieve/{bitstream-internal-id} - return configurationService - .getProperty("dspace.ui.url") - + "/retrieve/" - + String.valueOf(bitstream.getID()); - } + return configurationService + .getProperty("dspace.ui.url") + + "/bitstreams/" + + String.valueOf(bitstream.getID()) + + "/download"; } catch (SQLException e) { log.error("Database problem", e); - } catch (UnsupportedEncodingException e) { - log.error("Unknown character set", e); } // We should only get here if we failed to build a nice URL above diff --git a/dspace-api/src/main/java/org/dspace/ctask/general/CreateMissingIdentifiers.java b/dspace-api/src/main/java/org/dspace/ctask/general/CreateMissingIdentifiers.java new file mode 100644 index 0000000000..9639461426 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/ctask/general/CreateMissingIdentifiers.java @@ -0,0 +1,94 @@ +/** + * 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.ctask.general; + +import java.io.IOException; +import java.sql.SQLException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.curate.AbstractCurationTask; +import org.dspace.curate.Curator; +import org.dspace.identifier.IdentifierException; +import org.dspace.identifier.IdentifierProvider; +import org.dspace.identifier.VersionedHandleIdentifierProviderWithCanonicalHandles; +import org.dspace.identifier.factory.IdentifierServiceFactory; +import org.dspace.identifier.service.IdentifierService; +import org.dspace.services.factory.DSpaceServicesFactory; + +/** + * Ensure that an object has all of the identifiers that it should, minting them + * as necessary. + * + * @author Mark H. Wood {@literal } + */ +public class CreateMissingIdentifiers + extends AbstractCurationTask { + private static final Logger LOG = LogManager.getLogger(); + + @Override + public int perform(DSpaceObject dso) + throws IOException { + // Only some kinds of model objects get identifiers + if (!(dso instanceof Item)) { + return Curator.CURATE_SKIP; + } + + // XXX Temporary escape when an incompatible provider is configured. + // XXX Remove this when the provider is fixed. + boolean compatible = DSpaceServicesFactory + .getInstance() + .getServiceManager() + .getServiceByName( + VersionedHandleIdentifierProviderWithCanonicalHandles.class.getCanonicalName(), + IdentifierProvider.class) == null; + if (!compatible) { + setResult("This task is not compatible with VersionedHandleIdentifierProviderWithCanonicalHandles"); + return Curator.CURATE_ERROR; + } + // XXX End of escape + + String typeText = Constants.typeText[dso.getType()]; + + // Get a Context + Context context; + try { + context = Curator.curationContext(); + } catch (SQLException ex) { + report("Could not get the curation Context: " + ex.getMessage()); + return Curator.CURATE_ERROR; + } + + // Find the IdentifierService implementation + IdentifierService identifierService = IdentifierServiceFactory + .getInstance() + .getIdentifierService(); + + // Register any missing identifiers. + try { + identifierService.register(context, dso); + } catch (AuthorizeException | IdentifierException | SQLException ex) { + String message = ex.getMessage(); + report(String.format("Identifier(s) not minted for %s %s: %s%n", + typeText, dso.getID().toString(), message)); + LOG.error("Identifier(s) not minted: {}", message); + return Curator.CURATE_ERROR; + } + + // Success! + report(String.format("%s %s registered.%n", + typeText, dso.getID().toString())); + return Curator.CURATE_SUCCESS; + } +} diff --git a/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java b/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java index b430a0c973..f31feab612 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java +++ b/dspace-api/src/main/java/org/dspace/discovery/SolrSearchCore.java @@ -21,7 +21,6 @@ import org.apache.solr.client.solrj.impl.HttpSolrClient; import org.dspace.discovery.indexobject.IndexableItem; import org.dspace.service.impl.HttpConnectionPoolService; import org.dspace.services.ConfigurationService; -import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.storage.rdbms.DatabaseUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -75,14 +74,13 @@ public class SolrSearchCore { */ protected void initSolr() { if (solr == null) { - String solrService = DSpaceServicesFactory.getInstance().getConfigurationService() - .getProperty("discovery.search.server"); + String solrService = configurationService.getProperty("discovery.search.server"); UrlValidator urlValidator = new UrlValidator(UrlValidator.ALLOW_LOCAL_URLS); if (urlValidator.isValid(solrService) || configurationService .getBooleanProperty("discovery.solr.url.validation.enabled", true)) { try { - log.debug("Solr URL: " + solrService); + log.debug("Solr URL: {}", solrService); HttpSolrClient solrServer = new HttpSolrClient.Builder(solrService) .withHttpClient(httpConnectionPoolService.getClient()) .build(); @@ -103,10 +101,13 @@ public class SolrSearchCore { solr = solrServer; } catch (SolrServerException | IOException e) { - log.error("Error while initializing solr server", e); + log.error("Error while initializing solr server {}", + solrService, e); + throw new RuntimeException("Failed to contact Solr at " + solrService + + " : " + e.getMessage()); } } else { - log.error("Error while initializing solr, invalid url: " + solrService); + log.error("Error while initializing solr, invalid url: {}", solrService); } } } diff --git a/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java b/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java index 3dcd7d16a6..8ca5b7c0ea 100644 --- a/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java +++ b/dspace-api/src/main/java/org/dspace/external/provider/impl/OpenAIREFundingDataProvider.java @@ -15,6 +15,7 @@ import java.util.ArrayList; import java.util.Base64; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -33,6 +34,7 @@ import org.dspace.content.dto.MetadataValueDTO; import org.dspace.external.OpenAIRERestConnector; import org.dspace.external.model.ExternalDataObject; import org.dspace.external.provider.AbstractExternalDataProvider; +import org.dspace.importer.external.metadatamapping.MetadataFieldConfig; import org.springframework.beans.factory.annotation.Autowired; /** @@ -40,13 +42,9 @@ import org.springframework.beans.factory.annotation.Autowired; * will deal with the OpenAIRE External Data lookup * * @author paulo-graca - * */ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { - /** - * log4j logger - */ private static Logger log = org.apache.logging.log4j.LogManager.getLogger(OpenAIREFundingDataProvider.class); /** @@ -54,6 +52,16 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { */ protected static final String PREFIX = "info:eu-repo/grantAgreement"; + private static final String TITLE = "dcTitle"; + private static final String SUBJECT = "dcSubject"; + private static final String AWARD_URI = "awardURI"; + private static final String FUNDER_NAME = "funderName"; + private static final String SPATIAL = "coverageSpatial"; + private static final String AWARD_NUMBER = "awardNumber"; + private static final String FUNDER_ID = "funderIdentifier"; + private static final String FUNDING_STREAM = "fundingStream"; + private static final String TITLE_ALTERNATIVE = "titleAlternative"; + /** * rows default limit */ @@ -69,11 +77,9 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { */ protected OpenAIRERestConnector connector; - /** - * required method - */ - public void init() throws IOException { - } + protected Map metadataFields; + + public void init() throws IOException {} @Override public String getSourceIdentifier() { @@ -266,14 +272,22 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { } } + public Map getMetadataFields() { + return metadataFields; + } + + public void setMetadataFields(Map metadataFields) { + this.metadataFields = metadataFields; + } + /** * OpenAIRE Funding External Data Builder Class * * @author pgraca - * */ - public static class ExternalDataObjectBuilder { - ExternalDataObject externalDataObject; + public class ExternalDataObjectBuilder { + + private ExternalDataObject externalDataObject; public ExternalDataObjectBuilder(Project project) { String funderIdPrefix = "urn:openaire:"; @@ -283,46 +297,42 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { for (FundingTreeType fundingTree : projectHelper.getFundingTreeTypes()) { FunderType funder = fundingTree.getFunder(); // Funder name - this.addFunderName(funder.getName()); + this.addMetadata(metadataFields.get(FUNDER_NAME), funder.getName()); // Funder Id - convert it to an urn - this.addFunderID(funderIdPrefix + funder.getId()); + this.addMetadata(metadataFields.get(FUNDER_ID), funderIdPrefix + funder.getId()); // Jurisdiction - this.addFunderJuristiction(funder.getJurisdiction()); + this.addMetadata(metadataFields.get(SPATIAL), funder.getJurisdiction()); FundingHelper fundingHelper = new FundingHelper( - fundingTree.getFundingLevel2OrFundingLevel1OrFundingLevel0()); + fundingTree.getFundingLevel2OrFundingLevel1OrFundingLevel0()); // Funding description for (FundingType funding : fundingHelper.getFirstAvailableFunding()) { - this.addFundingStream(funding.getDescription()); + this.addMetadata(metadataFields.get(FUNDING_STREAM), funding.getDescription()); } } // Title for (String title : projectHelper.getTitles()) { - this.addAwardTitle(title); + this.addMetadata(metadataFields.get(TITLE), title); this.setDisplayValue(title); this.setValue(title); } - // Code for (String code : projectHelper.getCodes()) { - this.addAwardNumber(code); + this.addMetadata(metadataFields.get(AWARD_NUMBER), code); } - // Website url for (String url : projectHelper.getWebsiteUrls()) { - this.addAwardURI(url); + this.addMetadata(metadataFields.get(AWARD_URI), url); } - // Acronyms for (String acronym : projectHelper.getAcronyms()) { - this.addFundingItemAcronym(acronym); + this.addMetadata(metadataFields.get(TITLE_ALTERNATIVE), acronym); } - // Keywords for (String keyword : projectHelper.getKeywords()) { - this.addSubject(keyword); + this.addMetadata(metadataFields.get(SUBJECT), keyword); } } @@ -366,7 +376,6 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { * @return ExternalDataObjectBuilder */ public ExternalDataObjectBuilder setId(String id) { - // we use base64 encoding in order to use slashes / and other // characters that must be escaped for the <:entry-id> String base64Id = Base64.getEncoder().encodeToString(id.getBytes()); @@ -374,128 +383,10 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { return this; } - /** - * Add metadata dc.identifier - * - * @param metadata identifier - * @return ExternalDataObjectBuilder - */ - public ExternalDataObjectBuilder addIdentifier(String identifier) { - this.externalDataObject.addMetadata(new MetadataValueDTO("dc", "identifier", null, null, identifier)); - return this; - } - - /** - * Add metadata project.funder.name - * - * @param metadata funderName - * @return ExternalDataObjectBuilder - */ - public ExternalDataObjectBuilder addFunderName(String funderName) { - this.externalDataObject.addMetadata(new MetadataValueDTO("project", "funder", "name", null, funderName)); - return this; - } - - /** - * Add metadata project.funder.identifier - * - * @param metadata funderId - * @return ExternalDataObjectBuilder - */ - public ExternalDataObjectBuilder addFunderID(String funderID) { - this.externalDataObject - .addMetadata(new MetadataValueDTO("project", "funder", "identifier", null, funderID)); - return this; - } - - /** - * Add metadata dc.title - * - * @param metadata awardTitle - * @return ExternalDataObjectBuilder - */ - public ExternalDataObjectBuilder addAwardTitle(String awardTitle) { - this.externalDataObject.addMetadata(new MetadataValueDTO("dc", "title", null, null, awardTitle)); - return this; - } - - /** - * Add metadata oaire.fundingStream - * - * @param metadata fundingStream - * @return ExternalDataObjectBuilder - */ - public ExternalDataObjectBuilder addFundingStream(String fundingStream) { - this.externalDataObject - .addMetadata(new MetadataValueDTO("oaire", "fundingStream", null, null, fundingStream)); - return this; - } - - /** - * Add metadata oaire.awardNumber - * - * @param metadata awardNumber - * @return ExternalDataObjectBuilder - */ - public ExternalDataObjectBuilder addAwardNumber(String awardNumber) { - this.externalDataObject.addMetadata(new MetadataValueDTO("oaire", "awardNumber", null, null, awardNumber)); - return this; - } - - /** - * Add metadata oaire.awardURI - * - * @param metadata websiteUrl - * @return ExternalDataObjectBuilder - */ - public ExternalDataObjectBuilder addAwardURI(String websiteUrl) { - this.externalDataObject.addMetadata(new MetadataValueDTO("oaire", "awardURI", null, null, websiteUrl)); - return this; - } - - /** - * Add metadata dc.title.alternative - * - * @param metadata fundingItemAcronym - * @return ExternalDataObjectBuilder - */ - public ExternalDataObjectBuilder addFundingItemAcronym(String fundingItemAcronym) { - this.externalDataObject - .addMetadata(new MetadataValueDTO("dc", "title", "alternative", null, fundingItemAcronym)); - return this; - } - - /** - * Add metadata dc.coverage.spatial - * - * @param metadata funderJuristiction - * @return ExternalDataObjectBuilder - */ - public ExternalDataObjectBuilder addFunderJuristiction(String funderJuristiction) { - this.externalDataObject - .addMetadata(new MetadataValueDTO("dc", "coverage", "spatial", null, funderJuristiction)); - return this; - } - - /** - * Add metadata dc.description - * - * @param metadata description - * @return ExternalDataObjectBuilder - */ - public ExternalDataObjectBuilder addDescription(String description) { - this.externalDataObject.addMetadata(new MetadataValueDTO("dc", "description", null, null, description)); - return this; - } - - /** - * Add metadata dc.subject - * - * @param metadata subject - * @return ExternalDataObjectBuilder - */ - public ExternalDataObjectBuilder addSubject(String subject) { - this.externalDataObject.addMetadata(new MetadataValueDTO("dc", "subject", null, null, subject)); + public ExternalDataObjectBuilder addMetadata(MetadataFieldConfig metadataField, String value) { + this.externalDataObject.addMetadata(new MetadataValueDTO(metadataField.getSchema(), + metadataField.getElement(), + metadataField.getQualifier(), null, value)); return this; } @@ -508,4 +399,5 @@ public class OpenAIREFundingDataProvider extends AbstractExternalDataProvider { return this.externalDataObject; } } -} + +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/harvest/HarvestedCollectionServiceImpl.java b/dspace-api/src/main/java/org/dspace/harvest/HarvestedCollectionServiceImpl.java index d4bb37cf94..0ad83a3292 100644 --- a/dspace-api/src/main/java/org/dspace/harvest/HarvestedCollectionServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/harvest/HarvestedCollectionServiceImpl.java @@ -19,10 +19,8 @@ import java.util.Calendar; import java.util.Date; import java.util.List; import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.TransformerException; +import javax.xml.xpath.XPathExpressionException; -import ORG.oclc.oai.harvester2.verb.Identify; -import ORG.oclc.oai.harvester2.verb.ListIdentifiers; import org.dspace.content.Collection; import org.dspace.core.Context; import org.dspace.harvest.dao.HarvestedCollectionDAO; @@ -33,6 +31,8 @@ import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.Namespace; import org.jdom2.input.DOMBuilder; +import org.oclc.oai.harvester2.verb.Identify; +import org.oclc.oai.harvester2.verb.ListIdentifiers; import org.springframework.beans.factory.annotation.Autowired; import org.w3c.dom.DOMException; import org.xml.sax.SAXException; @@ -198,7 +198,7 @@ public class HarvestedCollectionServiceImpl implements HarvestedCollectionServic // First, see if we can contact the target server at all. try { new Identify(oaiSource); - } catch (IOException | ParserConfigurationException | TransformerException | SAXException ex) { + } catch (IOException | ParserConfigurationException | XPathExpressionException | SAXException ex) { errorSet.add(OAI_ADDRESS_ERROR + ": OAI server could not be reached."); return errorSet; } @@ -216,7 +216,7 @@ public class HarvestedCollectionServiceImpl implements HarvestedCollectionServic try { OREOAIPrefix = OAIHarvester.oaiResolveNamespaceToPrefix(oaiSource, OAIHarvester.getORENamespace().getURI()); DMDOAIPrefix = OAIHarvester.oaiResolveNamespaceToPrefix(oaiSource, DMD_NS.getURI()); - } catch (IOException | ParserConfigurationException | TransformerException | SAXException ex) { + } catch (IOException | ParserConfigurationException | XPathExpressionException | SAXException ex) { errorSet.add(OAI_ADDRESS_ERROR + ": OAI did not respond to ListMetadataFormats query (" + ORE_NS.getPrefix() + ":" + OREOAIPrefix + " ; " @@ -260,7 +260,8 @@ public class HarvestedCollectionServiceImpl implements HarvestedCollectionServic } } } - } catch (IOException | ParserConfigurationException | TransformerException | DOMException | SAXException e) { + } catch (IOException | ParserConfigurationException | XPathExpressionException | DOMException | + SAXException e) { errorSet.add(OAI_ADDRESS_ERROR + ": OAI server could not be reached"); return errorSet; } catch (RuntimeException re) { diff --git a/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java b/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java index a0cc9bcc68..5a6b26da93 100644 --- a/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java +++ b/dspace-api/src/main/java/org/dspace/harvest/OAIHarvester.java @@ -28,12 +28,8 @@ import java.util.Map; import java.util.Set; import java.util.TimeZone; import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.TransformerException; +import javax.xml.xpath.XPathExpressionException; -import ORG.oclc.oai.harvester2.verb.GetRecord; -import ORG.oclc.oai.harvester2.verb.Identify; -import ORG.oclc.oai.harvester2.verb.ListMetadataFormats; -import ORG.oclc.oai.harvester2.verb.ListRecords; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -76,6 +72,10 @@ import org.jdom2.Element; import org.jdom2.Namespace; import org.jdom2.input.DOMBuilder; import org.jdom2.output.XMLOutputter; +import org.oclc.oai.harvester2.verb.GetRecord; +import org.oclc.oai.harvester2.verb.Identify; +import org.oclc.oai.harvester2.verb.ListMetadataFormats; +import org.oclc.oai.harvester2.verb.ListRecords; import org.xml.sax.SAXException; @@ -493,11 +493,11 @@ public class OAIHarvester { * @throws HarvestingException if harvesting error * @throws ParserConfigurationException XML parsing error * @throws SAXException if XML processing error - * @throws TransformerException if XML transformer error + * @throws XPathExpressionException if XPath error */ protected void processRecord(Element record, String OREPrefix, final long currentRecord, long totalListSize) throws SQLException, AuthorizeException, IOException, CrosswalkException, HarvestingException, - ParserConfigurationException, SAXException, TransformerException { + ParserConfigurationException, SAXException, XPathExpressionException { WorkspaceItem wi = null; Date timeStart = new Date(); @@ -769,10 +769,10 @@ public class OAIHarvester { * @throws IOException if IO error * @throws SAXException if XML processing error * @throws ParserConfigurationException XML parsing error - * @throws TransformerException if XML transformer error + * @throws XPathExpressionException if XPath error */ private String oaiGetDateGranularity(String oaiSource) - throws IOException, ParserConfigurationException, SAXException, TransformerException { + throws IOException, ParserConfigurationException, SAXException, XPathExpressionException { Identify iden = new Identify(oaiSource); return iden.getDocument().getElementsByTagNameNS(OAI_NS.getURI(), "granularity").item(0).getTextContent(); } @@ -789,11 +789,11 @@ public class OAIHarvester { * operations. * @throws ParserConfigurationException XML parsing error * @throws SAXException if XML processing error - * @throws TransformerException if XML transformer error + * @throws XPathExpressionException if XPath error * @throws ConnectException if could not connect to OAI server */ public static String oaiResolveNamespaceToPrefix(String oaiSource, String MDNamespace) - throws IOException, ParserConfigurationException, SAXException, TransformerException, ConnectException { + throws IOException, ParserConfigurationException, SAXException, XPathExpressionException, ConnectException { String metaPrefix = null; // Query the OAI server for the metadata @@ -866,11 +866,11 @@ public class OAIHarvester { * operations. * @throws ParserConfigurationException XML parsing error * @throws SAXException if XML processing error - * @throws TransformerException if XML transformer error + * @throws XPathExpressionException if XPath error * @throws HarvestingException if harvesting error */ protected List getMDrecord(String oaiSource, String itemOaiId, String metadataPrefix) - throws IOException, ParserConfigurationException, SAXException, TransformerException, HarvestingException { + throws IOException, ParserConfigurationException, SAXException, XPathExpressionException, HarvestingException { GetRecord getRecord = new GetRecord(oaiSource, itemOaiId, metadataPrefix); Set errorSet = new HashSet<>(); // If the metadata is not available for this item, can the whole thing diff --git a/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java b/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java index 74219fc71c..64eee1dfcf 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java +++ b/dspace-api/src/main/java/org/dspace/identifier/service/IdentifierService.java @@ -92,6 +92,9 @@ public interface IdentifierService { throws AuthorizeException, SQLException, IdentifierException; /** + * Used to register newly-minted identifiers. Each provider is responsible + * for creating the appropriate identifier. All providers are interrogated. + * * @param context The relevant DSpace Context. * @param dso DSpace object to be registered * @throws AuthorizeException if authorization error @@ -101,7 +104,7 @@ public interface IdentifierService { void register(Context context, DSpaceObject dso) throws AuthorizeException, SQLException, IdentifierException; /** - * Used to Register a specific Identifier (for example a Handle, hdl:1234.5/6) + * Used to Register a specific Identifier (for example a Handle, hdl:1234.5/6). * The provider is responsible for detecting and processing the appropriate * identifier. All Providers are interrogated. Multiple providers * can process the same identifier. diff --git a/dspace-api/src/main/java/org/dspace/importer/external/bibtex/service/BibtexImportMetadataSourceServiceImpl.java b/dspace-api/src/main/java/org/dspace/importer/external/bibtex/service/BibtexImportMetadataSourceServiceImpl.java index 7468d601f5..445dfedebd 100644 --- a/dspace-api/src/main/java/org/dspace/importer/external/bibtex/service/BibtexImportMetadataSourceServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/importer/external/bibtex/service/BibtexImportMetadataSourceServiceImpl.java @@ -70,11 +70,24 @@ public class BibtexImportMetadataSourceServiceImpl extends AbstractPlainMetadata keyValueItem.setKey(entry.getValue().getType().getValue()); keyValueItem.setValue(entry.getKey().getValue()); keyValues.add(keyValueItem); + PlainMetadataKeyValueItem typeItem = new PlainMetadataKeyValueItem(); + typeItem.setKey("type"); + typeItem.setValue(entry.getValue().getType().getValue()); + keyValues.add(typeItem); if (entry.getValue().getFields() != null) { for (Entry subentry : entry.getValue().getFields().entrySet()) { PlainMetadataKeyValueItem innerItem = new PlainMetadataKeyValueItem(); - innerItem.setKey(subentry.getKey().getValue()); - innerItem.setValue(subentry.getValue().toUserString()); + innerItem.setKey(subentry.getKey().getValue().toLowerCase()); + String latexString = subentry.getValue().toUserString(); + try { + org.jbibtex.LaTeXParser laTeXParser = new org.jbibtex.LaTeXParser(); + List latexObjects = laTeXParser.parse(latexString); + org.jbibtex.LaTeXPrinter laTeXPrinter = new org.jbibtex.LaTeXPrinter(); + String plainTextString = laTeXPrinter.print(latexObjects); + innerItem.setValue(plainTextString.replaceAll("\n", " ")); + } catch (ParseException e) { + innerItem.setValue(latexString); + } keyValues.add(innerItem); } } diff --git a/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SplitMetadataContributor.java b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SplitMetadataContributor.java new file mode 100644 index 0000000000..c04081957f --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/importer/external/metadatamapping/contributor/SplitMetadataContributor.java @@ -0,0 +1,65 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.importer.external.metadatamapping.contributor; + +import java.util.ArrayList; +import java.util.Collection; + +import org.dspace.importer.external.metadatamapping.MetadataFieldMapping; +import org.dspace.importer.external.metadatamapping.MetadatumDTO; + +/** + * Wrapper class used to split another MetadataContributor's output into distinct values. + * The split is performed by matching a regular expression against the wrapped MetadataContributor's output. + * + * @author Philipp Rumpf (philipp.rumpf@uni-bamberg.de) + */ + +public class SplitMetadataContributor implements MetadataContributor { + private final MetadataContributor innerContributor; + private final String regex; + + /** + * @param innerContributor The MetadataContributor whose output is split + * @param regex A regular expression matching the separator between different values + */ + public SplitMetadataContributor(MetadataContributor innerContributor, String regex) { + this.innerContributor = innerContributor; + this.regex = regex; + } + + @Override + public void setMetadataFieldMapping(MetadataFieldMapping> rt) { + + } + + /** + * Each metadatum returned by the wrapped MetadataContributor is split into one or more metadata values + * based on the provided regular expression. + * + * @param t The recordType object to retrieve metadata from + * @return The collection of processed metadata values + */ + @Override + public Collection contributeMetadata(T t) { + Collection metadata = innerContributor.contributeMetadata(t); + ArrayList splitMetadata = new ArrayList<>(); + for (MetadatumDTO metadatumDTO : metadata) { + String[] split = metadatumDTO.getValue().split(regex); + for (String splitItem : split) { + MetadatumDTO splitMetadatumDTO = new MetadatumDTO(); + splitMetadatumDTO.setSchema(metadatumDTO.getSchema()); + splitMetadatumDTO.setElement(metadatumDTO.getElement()); + splitMetadatumDTO.setQualifier(metadatumDTO.getQualifier()); + splitMetadatumDTO.setValue(splitItem); + splitMetadata.add(splitMetadatumDTO); + } + } + return splitMetadata; + } +} diff --git a/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDataVisits.java b/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDataVisits.java index 4ee7a0f3e4..121e66af48 100644 --- a/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDataVisits.java +++ b/dspace-api/src/main/java/org/dspace/statistics/content/StatisticsDataVisits.java @@ -621,6 +621,10 @@ public class StatisticsDataVisits extends StatisticsData { } if (dsoId != null && query.dsoType != -1) { + // Store the UUID of the DSO as an attribute. Needed in particular for Bitstream download usage reports, + // as the Bitstream itself won't be available when converting points to their REST representation + attrs.put("id", dsoId); + switch (query.dsoType) { case Constants.BITSTREAM: Bitstream bit = bitstreamService.findByIdOrLegacyId(context, dsoId); diff --git a/dspace-api/src/main/java/org/dspace/submit/model/UploadConfiguration.java b/dspace-api/src/main/java/org/dspace/submit/model/UploadConfiguration.java index bc2f117b3c..a6421b3f7a 100644 --- a/dspace-api/src/main/java/org/dspace/submit/model/UploadConfiguration.java +++ b/dspace-api/src/main/java/org/dspace/submit/model/UploadConfiguration.java @@ -8,15 +8,17 @@ package org.dspace.submit.model; import java.util.List; +import javax.inject.Inject; import org.dspace.services.ConfigurationService; /** + * A collection of conditions to be met when uploading Bitstreams. * @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it) */ public class UploadConfiguration { - private ConfigurationService configurationService; + private final ConfigurationService configurationService; private String metadataDefinition; private List options; @@ -24,22 +26,52 @@ public class UploadConfiguration { private Boolean required; private String name; + /** + * Construct a bitstream uploading configuration. + * @param configurationService DSpace configuration provided by the DI container. + */ + @Inject + public UploadConfiguration(ConfigurationService configurationService) { + this.configurationService = configurationService; + } + + /** + * The list of access restriction types from which a submitter may choose. + * @return choices for restricting access to Bitstreams. + */ public List getOptions() { return options; } + /** + * Set the list of access restriction types from which to choose. + * Required. May be empty. + * @param options choices for restricting access to Bitstreams. + */ public void setOptions(List options) { this.options = options; } + /** + * Name of the submission form to which these conditions are attached. + * @return the form's name. + */ public String getMetadata() { return metadataDefinition; } + /** + * Name the submission form to which these conditions are attached. + * @param metadata the form's name. + */ public void setMetadata(String metadata) { this.metadataDefinition = metadata; } + /** + * Limit on the maximum size of an uploaded Bitstream. + * @return maximum upload size in bytes. + */ public Long getMaxSize() { if (maxSize == null) { maxSize = configurationService.getLongProperty("upload.max"); @@ -47,10 +79,18 @@ public class UploadConfiguration { return maxSize; } + /** + * Limit the maximum size of an uploaded Bitstream. + * @param maxSize maximum upload size in bytes. + */ public void setMaxSize(Long maxSize) { this.maxSize = maxSize; } + /** + * Is at least one Bitstream required when submitting a new Item? + * @return true if a Bitstream is required. + */ public Boolean isRequired() { if (required == null) { //defaults to true @@ -60,25 +100,27 @@ public class UploadConfiguration { return required; } + /** + * Is at least one Bitstream required when submitting a new Item? + * @param required true if a Bitstream is required. + */ public void setRequired(Boolean required) { this.required = required; } - public ConfigurationService getConfigurationService() { - return configurationService; - } - - public void setConfigurationService(ConfigurationService configurationService) { - this.configurationService = configurationService; - } - + /** + * The unique name of this configuration. + * @return configuration's name. + */ public String getName() { return name; } + /** + * Give this configuration a unique name. Required. + * @param name configuration's name. + */ public void setName(String name) { this.name = name; } - - } diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml index e21a85cca4..450ed3ad0b 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/access-conditions.xml @@ -3,10 +3,9 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> - + - - + @@ -18,7 +17,7 @@ - + @@ -31,7 +30,7 @@ - + @@ -43,13 +42,13 @@ - + diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml index e10d04a16f..f1e6c30d13 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/external-openaire.xml @@ -1,6 +1,10 @@ - @@ -15,11 +19,71 @@ init-method="init"> + Project + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/app/requestitem/CollectionAdministratorsRequestItemStrategyTest.java b/dspace-api/src/test/java/org/dspace/app/requestitem/CollectionAdministratorsRequestItemStrategyTest.java new file mode 100644 index 0000000000..37292e91c8 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/requestitem/CollectionAdministratorsRequestItemStrategyTest.java @@ -0,0 +1,62 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.requestitem; + +import static org.junit.Assert.assertEquals; + +import java.util.List; + +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.junit.Test; +import org.mockito.Mockito; + +/** + * + * @author Mark H. Wood + */ +public class CollectionAdministratorsRequestItemStrategyTest { + private static final String NAME = "John Q. Public"; + private static final String EMAIL = "jqpublic@example.com"; + + /** + * Test of getRequestItemAuthor method, of class CollectionAdministratorsRequestItemStrategy. + * @throws java.lang.Exception passed through. + */ + @Test + public void testGetRequestItemAuthor() + throws Exception { + System.out.println("getRequestItemAuthor"); + + Context context = Mockito.mock(Context.class); + + EPerson eperson1 = Mockito.mock(EPerson.class); + Mockito.when(eperson1.getEmail()).thenReturn(EMAIL); + Mockito.when(eperson1.getFullName()).thenReturn(NAME); + + Group group1 = Mockito.mock(Group.class); + Mockito.when(group1.getMembers()).thenReturn(List.of(eperson1)); + + Collection collection1 = Mockito.mock(Collection.class); + Mockito.when(collection1.getAdministrators()).thenReturn(group1); + + Item item = Mockito.mock(Item.class); + Mockito.when(item.getOwningCollection()).thenReturn(collection1); + Mockito.when(item.getSubmitter()).thenReturn(eperson1); + + CollectionAdministratorsRequestItemStrategy instance = new CollectionAdministratorsRequestItemStrategy(); + List result = instance.getRequestItemAuthor(context, + item); + assertEquals("Should be one author", 1, result.size()); + assertEquals("Name should match " + NAME, NAME, result.get(0).getFullName()); + assertEquals("Email should match " + EMAIL, EMAIL, result.get(0).getEmail()); + } +} diff --git a/dspace-api/src/test/java/org/dspace/app/requestitem/CombiningRequestItemStrategyTest.java b/dspace-api/src/test/java/org/dspace/app/requestitem/CombiningRequestItemStrategyTest.java new file mode 100644 index 0000000000..c5475612cb --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/app/requestitem/CombiningRequestItemStrategyTest.java @@ -0,0 +1,53 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.requestitem; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; + +import java.util.List; + +import org.dspace.content.Item; +import org.dspace.core.Context; +import org.junit.Test; +import org.mockito.Mockito; + +/** + * + * @author Mark H. Wood + */ +public class CombiningRequestItemStrategyTest { + /** + * Test of getRequestItemAuthor method, of class CombiningRequestItemStrategy. + * @throws java.lang.Exception passed through. + */ + @Test + public void testGetRequestItemAuthor() + throws Exception { + System.out.println("getRequestItemAuthor"); + Context context = null; + + Item item = Mockito.mock(Item.class); + RequestItemAuthor author1 = new RequestItemAuthor("Pat Paulsen", "ppaulsen@example.com"); + RequestItemAuthor author2 = new RequestItemAuthor("Alfred E. Neuman", "aeneuman@example.com"); + RequestItemAuthor author3 = new RequestItemAuthor("Alias Undercover", "aundercover@example.com"); + + RequestItemAuthorExtractor strategy1 = Mockito.mock(RequestItemHelpdeskStrategy.class); + Mockito.when(strategy1.getRequestItemAuthor(context, item)).thenReturn(List.of(author1)); + + RequestItemAuthorExtractor strategy2 = Mockito.mock(RequestItemMetadataStrategy.class); + Mockito.when(strategy2.getRequestItemAuthor(context, item)).thenReturn(List.of(author2, author3)); + + List strategies = List.of(strategy1, strategy2); + + CombiningRequestItemStrategy instance = new CombiningRequestItemStrategy(strategies); + List result = instance.getRequestItemAuthor(context, + item); + assertThat(result, containsInAnyOrder(author1, author2, author3)); + } +} diff --git a/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java b/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java new file mode 100644 index 0000000000..383f87bbd7 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/ctask/general/CreateMissingIdentifiersIT.java @@ -0,0 +1,79 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.ctask.general; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; + +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.dspace.curate.Curator; +import org.dspace.identifier.VersionedHandleIdentifierProviderWithCanonicalHandles; +import org.dspace.services.ConfigurationService; +import org.dspace.services.factory.DSpaceServicesFactory; +import org.junit.Test; + +/** + * Rudimentary test of the curation task. + * + * @author mwood + */ +public class CreateMissingIdentifiersIT + extends AbstractIntegrationTestWithDatabase { + private static final String P_TASK_DEF + = "plugin.named.org.dspace.curate.CurationTask"; + private static final String TASK_NAME = "test"; + + @Test + public void testPerform() + throws IOException { + ConfigurationService configurationService + = DSpaceServicesFactory.getInstance().getConfigurationService(); + configurationService.setProperty(P_TASK_DEF, null); + configurationService.addPropertyValue(P_TASK_DEF, + CreateMissingIdentifiers.class.getCanonicalName() + " = " + TASK_NAME); + + Curator curator = new Curator(); + curator.addTask(TASK_NAME); + + context.setCurrentUser(admin); + parentCommunity = CommunityBuilder.createCommunity(context) + .build(); + Collection collection = CollectionBuilder.createCollection(context, parentCommunity) + .build(); + Item item = ItemBuilder.createItem(context, collection) + .build(); + + /* + * Curate with regular test configuration -- should succeed. + */ + curator.curate(context, item); + int status = curator.getStatus(TASK_NAME); + assertEquals("Curation should succeed", Curator.CURATE_SUCCESS, status); + + /* + * Now install an incompatible provider to make the task fail. + */ + DSpaceServicesFactory.getInstance() + .getServiceManager() + .registerServiceClass( + VersionedHandleIdentifierProviderWithCanonicalHandles.class.getCanonicalName(), + VersionedHandleIdentifierProviderWithCanonicalHandles.class); + + curator.curate(context, item); + System.out.format("With incompatible provider, result is '%s'.\n", + curator.getResult(TASK_NAME)); + assertEquals("Curation should fail", Curator.CURATE_ERROR, + curator.getStatus(TASK_NAME)); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java index d013566a2c..82f9ed3318 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/RequestItemRepository.java @@ -15,7 +15,9 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.sql.SQLException; +import java.util.Collections; import java.util.Date; +import java.util.List; import java.util.UUID; import javax.servlet.http.HttpServletRequest; @@ -229,14 +231,20 @@ public class RequestItemRepository } // Check for authorized user - RequestItemAuthor authorizer; + List authorizers; try { - authorizer = requestItemAuthorExtractor.getRequestItemAuthor(context, ri.getItem()); + authorizers = requestItemAuthorExtractor.getRequestItemAuthor(context, ri.getItem()); } catch (SQLException ex) { LOG.warn("Failed to find an authorizer: {}", ex.getMessage()); - authorizer = new RequestItemAuthor("", ""); + authorizers = Collections.EMPTY_LIST; } - if (!authorizer.getEmail().equals(context.getCurrentUser().getEmail())) { + + boolean authorized = false; + String requester = context.getCurrentUser().getEmail(); + for (RequestItemAuthor authorizer : authorizers) { + authorized |= authorizer.getEmail().equals(requester); + } + if (!authorized) { throw new AuthorizeException("Not authorized to approve this request"); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/UsageReportUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/UsageReportUtils.java index ca10b9face..4603569da8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/UsageReportUtils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/UsageReportUtils.java @@ -251,7 +251,10 @@ public class UsageReportUtils { for (int i = 0; i < dataset.getColLabels().size(); i++) { UsageReportPointDsoTotalVisitsRest totalDownloadsPoint = new UsageReportPointDsoTotalVisitsRest(); totalDownloadsPoint.setType("bitstream"); - totalDownloadsPoint.setId(dataset.getColLabels().get(i)); + + totalDownloadsPoint.setId(dataset.getColLabelsAttrs().get(i).get("id")); + totalDownloadsPoint.setLabel(dataset.getColLabels().get(i)); + totalDownloadsPoint.addValue("views", Integer.valueOf(dataset.getMatrix()[0][i])); usageReportRest.addPoint(totalDownloadsPoint); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/StatisticsRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/StatisticsRestRepositoryIT.java index 5dd24451c5..5544ecdb03 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/StatisticsRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/StatisticsRestRepositoryIT.java @@ -22,13 +22,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.ArrayList; -import java.util.Arrays; import java.util.Calendar; import java.util.List; import java.util.Locale; import java.util.UUID; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.matcher.UsageReportMatcher; import org.dspace.app.rest.model.UsageReportPointCityRest; import org.dspace.app.rest.model.UsageReportPointCountryRest; @@ -50,6 +50,7 @@ import org.dspace.builder.SiteBuilder; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; +import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.Site; import org.dspace.core.Constants; @@ -281,42 +282,40 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .contentType(contentType)) .andExpect(status().isCreated()); - UsageReportPointDsoTotalVisitsRest expectedPoint = new UsageReportPointDsoTotalVisitsRest(); - expectedPoint.addValue("views", 1); - expectedPoint.setType("community"); - expectedPoint.setId(communityVisited.getID().toString()); - // And request that community's TotalVisits stat report getClient(adminToken).perform( get("/api/statistics/usagereports/" + communityVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID)) // ** THEN ** .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is( - UsageReportMatcher - .matchUsageReport(communityVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, - TOTAL_VISITS_REPORT_ID, Arrays.asList(expectedPoint)))) - ); + UsageReportMatcher.matchUsageReport( + communityVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, + TOTAL_VISITS_REPORT_ID, + List.of( + getExpectedDsoViews(communityVisited, 1) + ) + ) + ))); } @Test public void totalVisitsReport_Community_NotVisited() throws Exception { // ** WHEN ** // Community is never visited - UsageReportPointDsoTotalVisitsRest expectedPoint = new UsageReportPointDsoTotalVisitsRest(); - expectedPoint.addValue("views", 0); - expectedPoint.setType("community"); - expectedPoint.setId(communityNotVisited.getID().toString()); - // And request that community's TotalVisits stat report getClient(adminToken).perform( get("/api/statistics/usagereports/" + communityNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID)) // ** THEN ** .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is( - UsageReportMatcher - .matchUsageReport(communityNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, - TOTAL_VISITS_REPORT_ID, Arrays.asList(expectedPoint)))) - ); + UsageReportMatcher.matchUsageReport( + communityNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, + TOTAL_VISITS_REPORT_ID, + List.of( + getExpectedDsoViews(communityNotVisited, 0) + ) + ) + ))); } @Test @@ -339,42 +338,40 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .contentType(contentType)) .andExpect(status().isCreated()); - UsageReportPointDsoTotalVisitsRest expectedPoint = new UsageReportPointDsoTotalVisitsRest(); - expectedPoint.addValue("views", 2); - expectedPoint.setType("collection"); - expectedPoint.setId(collectionVisited.getID().toString()); - // And request that collection's TotalVisits stat report getClient(adminToken).perform( get("/api/statistics/usagereports/" + collectionVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID)) // ** THEN ** .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is( - UsageReportMatcher - .matchUsageReport(collectionVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, - TOTAL_VISITS_REPORT_ID, Arrays.asList(expectedPoint)))) - ); + UsageReportMatcher.matchUsageReport( + collectionVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, + TOTAL_VISITS_REPORT_ID, + List.of( + getExpectedDsoViews(collectionVisited, 2) + ) + ) + ))); } @Test public void totalVisitsReport_Collection_NotVisited() throws Exception { // ** WHEN ** // Collection is never visited - UsageReportPointDsoTotalVisitsRest expectedPoint = new UsageReportPointDsoTotalVisitsRest(); - expectedPoint.addValue("views", 0); - expectedPoint.setType("collection"); - expectedPoint.setId(collectionNotVisited.getID().toString()); - // And request that collection's TotalVisits stat report getClient(adminToken).perform( get("/api/statistics/usagereports/" + collectionNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID)) // ** THEN ** .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is( - UsageReportMatcher - .matchUsageReport(collectionNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, - TOTAL_VISITS_REPORT_ID, Arrays.asList(expectedPoint)))) - ); + UsageReportMatcher.matchUsageReport( + collectionNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, + TOTAL_VISITS_REPORT_ID, + List.of( + getExpectedDsoViews(collectionNotVisited, 0) + ) + ) + ))); } @Test @@ -392,42 +389,42 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .contentType(contentType)) .andExpect(status().isCreated()); - UsageReportPointDsoTotalVisitsRest expectedPoint = new UsageReportPointDsoTotalVisitsRest(); - expectedPoint.addValue("views", 1); - expectedPoint.setType("item"); - expectedPoint.setId(itemVisited.getID().toString()); - // And request that collection's TotalVisits stat report getClient(adminToken).perform( get("/api/statistics/usagereports/" + itemVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID)) // ** THEN ** .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is( - UsageReportMatcher - .matchUsageReport(itemVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, - TOTAL_VISITS_REPORT_ID, Arrays.asList(expectedPoint)))) - ); + UsageReportMatcher.matchUsageReport( + itemVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, + TOTAL_VISITS_REPORT_ID, + List.of( + getExpectedDsoViews(itemVisited, 1) + ) + ) + ))); } @Test public void totalVisitsReport_Item_NotVisited() throws Exception { // ** WHEN ** //Item is never visited - UsageReportPointDsoTotalVisitsRest expectedPoint = new UsageReportPointDsoTotalVisitsRest(); - expectedPoint.addValue("views", 0); - expectedPoint.setType("item"); - expectedPoint.setId(itemNotVisitedWithBitstreams.getID().toString()); + List expectedPoints = List.of( + getExpectedDsoViews(itemNotVisitedWithBitstreams, 0) + ); // And request that item's TotalVisits stat report getClient(adminToken).perform( get("/api/statistics/usagereports/" + itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_VISITS_REPORT_ID)) // ** THEN ** .andExpect(status().isOk()) - .andExpect(jsonPath("$", Matchers.is( - UsageReportMatcher - .matchUsageReport(itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_VISITS_REPORT_ID, - TOTAL_VISITS_REPORT_ID, Arrays.asList(expectedPoint)))) - ); + .andExpect(jsonPath("$", Matchers.is( + UsageReportMatcher.matchUsageReport( + itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_VISITS_REPORT_ID, + TOTAL_VISITS_REPORT_ID, + expectedPoints + ) + ))); // only admin access visits report getClient(loggedInToken).perform( @@ -445,19 +442,25 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes get("/api/statistics/usagereports/" + itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_VISITS_REPORT_ID)) .andExpect(status().isOk()) - .andExpect(jsonPath("$", - Matchers.is(UsageReportMatcher.matchUsageReport( - itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_VISITS_REPORT_ID, - TOTAL_VISITS_REPORT_ID, Arrays.asList(expectedPoint))))); + .andExpect(jsonPath("$", Matchers.is( + UsageReportMatcher.matchUsageReport( + itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_VISITS_REPORT_ID, + TOTAL_VISITS_REPORT_ID, + expectedPoints + ) + ))); - getClient().perform( + getClient().perform( get("/api/statistics/usagereports/" + itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_VISITS_REPORT_ID)) .andExpect(status().isOk()) - .andExpect(jsonPath("$", - Matchers.is(UsageReportMatcher.matchUsageReport( - itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_VISITS_REPORT_ID, - TOTAL_VISITS_REPORT_ID, Arrays.asList(expectedPoint))))); + .andExpect(jsonPath("$", Matchers.is( + UsageReportMatcher.matchUsageReport( + itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_VISITS_REPORT_ID, + TOTAL_VISITS_REPORT_ID, + expectedPoints + ) + ))); } @Test @@ -475,10 +478,9 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .contentType(contentType)) .andExpect(status().isCreated()); - UsageReportPointDsoTotalVisitsRest expectedPoint = new UsageReportPointDsoTotalVisitsRest(); - expectedPoint.addValue("views", 1); - expectedPoint.setType("bitstream"); - expectedPoint.setId(bitstreamVisited.getID().toString()); + List expectedPoints = List.of( + getExpectedDsoViews(bitstreamVisited, 1) + ); // And request that bitstream's TotalVisits stat report getClient(adminToken).perform( @@ -486,10 +488,12 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes // ** THEN ** .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is( - UsageReportMatcher - .matchUsageReport(bitstreamVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, - TOTAL_VISITS_REPORT_ID, Arrays.asList(expectedPoint)))) - ); + UsageReportMatcher.matchUsageReport( + bitstreamVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, + TOTAL_VISITS_REPORT_ID, + expectedPoints + ) + ))); // only admin access visits report getClient(loggedInToken).perform( @@ -506,28 +510,34 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes getClient(loggedInToken).perform( get("/api/statistics/usagereports/" + bitstreamVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID)) .andExpect(status().isOk()) - .andExpect(jsonPath("$", - Matchers.is(UsageReportMatcher.matchUsageReport( - bitstreamVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, TOTAL_VISITS_REPORT_ID, - Arrays.asList(expectedPoint))))); + .andExpect(jsonPath("$", Matchers.is( + UsageReportMatcher.matchUsageReport( + bitstreamVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, + TOTAL_VISITS_REPORT_ID, + expectedPoints + ) + ))); getClient().perform( get("/api/statistics/usagereports/" + bitstreamVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID)) .andExpect(status().isOk()) - .andExpect(jsonPath("$", - Matchers.is(UsageReportMatcher.matchUsageReport( - bitstreamVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, TOTAL_VISITS_REPORT_ID, - Arrays.asList(expectedPoint))))); + .andExpect(jsonPath("$", Matchers.is( + UsageReportMatcher.matchUsageReport( + bitstreamVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, + TOTAL_VISITS_REPORT_ID, + expectedPoints + ) + ))); } @Test public void totalVisitsReport_Bitstream_NotVisited() throws Exception { // ** WHEN ** // Bitstream is never visited - UsageReportPointDsoTotalVisitsRest expectedPoint = new UsageReportPointDsoTotalVisitsRest(); - expectedPoint.addValue("views", 0); - expectedPoint.setType("bitstream"); - expectedPoint.setId(bitstreamNotVisited.getID().toString()); + + List expectedPoints = List.of( + getExpectedDsoViews(bitstreamNotVisited, 0) + ); String authToken = getAuthToken(admin.getEmail(), password); // And request that bitstream's TotalVisits stat report @@ -536,10 +546,12 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes // ** THEN ** .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is( - UsageReportMatcher - .matchUsageReport(bitstreamNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, - TOTAL_VISITS_REPORT_ID, Arrays.asList(expectedPoint)))) - ); + UsageReportMatcher.matchUsageReport( + bitstreamNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, + TOTAL_VISITS_REPORT_ID, + expectedPoints + ) + ))); String tokenEPerson = getAuthToken(eperson.getEmail(), password); getClient(tokenEPerson).perform( @@ -556,18 +568,24 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes getClient(tokenEPerson).perform( get("/api/statistics/usagereports/" + bitstreamNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID)) .andExpect(status().isOk()) - .andExpect(jsonPath("$", - Matchers.is(UsageReportMatcher.matchUsageReport( - bitstreamNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, TOTAL_VISITS_REPORT_ID, - Arrays.asList(expectedPoint))))); + .andExpect(jsonPath("$", Matchers.is( + UsageReportMatcher.matchUsageReport( + bitstreamNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, + TOTAL_VISITS_REPORT_ID, + expectedPoints + ) + ))); getClient().perform( get("/api/statistics/usagereports/" + bitstreamNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID)) .andExpect(status().isOk()) - .andExpect(jsonPath("$", - Matchers.is(UsageReportMatcher.matchUsageReport( - bitstreamNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, TOTAL_VISITS_REPORT_ID, - Arrays.asList(expectedPoint))))); + .andExpect(jsonPath("$", Matchers.is( + UsageReportMatcher.matchUsageReport( + bitstreamNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, + TOTAL_VISITS_REPORT_ID, + expectedPoints + ) + ))); } @Test @@ -593,9 +611,12 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes // ** THEN ** .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is( - UsageReportMatcher - .matchUsageReport(itemVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID, - TOTAL_VISITS_PER_MONTH_REPORT_ID, expectedPoints)))); + UsageReportMatcher.matchUsageReport( + itemVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID, + TOTAL_VISITS_PER_MONTH_REPORT_ID, + expectedPoints + ) + ))); // only admin has access getClient(loggedInToken).perform( @@ -612,26 +633,30 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes getClient(loggedInToken).perform( get("/api/statistics/usagereports/" + itemVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID)) .andExpect(status().isOk()) - .andExpect(jsonPath("$", - Matchers.is(UsageReportMatcher.matchUsageReport( - itemVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID, - TOTAL_VISITS_PER_MONTH_REPORT_ID, expectedPoints)))); + .andExpect(jsonPath("$", Matchers.is( + UsageReportMatcher.matchUsageReport( + itemVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID, + TOTAL_VISITS_PER_MONTH_REPORT_ID, + expectedPoints + ) + ))); getClient().perform( get("/api/statistics/usagereports/" + itemVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID)) .andExpect(status().isOk()) - .andExpect(jsonPath("$", - Matchers.is(UsageReportMatcher.matchUsageReport( - itemVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID, - TOTAL_VISITS_PER_MONTH_REPORT_ID, expectedPoints)))); + .andExpect(jsonPath("$", Matchers.is( + UsageReportMatcher.matchUsageReport( + itemVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID, + TOTAL_VISITS_PER_MONTH_REPORT_ID, + expectedPoints + ) + ))); } @Test public void totalVisitsPerMonthReport_Item_NotVisited() throws Exception { // ** WHEN ** // Item is not visited - List expectedPoints = this.getListOfVisitsPerMonthsPoints(0); - // And request that item's TotalVisitsPerMonth stat report getClient(adminToken).perform( get("/api/statistics/usagereports/" + itemNotVisitedWithBitstreams.getID() + "_" + @@ -639,10 +664,12 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes // ** THEN ** .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is( - UsageReportMatcher - .matchUsageReport( + UsageReportMatcher.matchUsageReport( itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID, - TOTAL_VISITS_PER_MONTH_REPORT_ID, expectedPoints)))); + TOTAL_VISITS_PER_MONTH_REPORT_ID, + this.getListOfVisitsPerMonthsPoints(0) + ) + ))); } @Test @@ -665,17 +692,18 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .contentType(contentType)) .andExpect(status().isCreated()); - List expectedPoints = this.getListOfVisitsPerMonthsPoints(2); - // And request that collection's TotalVisitsPerMonth stat report getClient(adminToken).perform( get("/api/statistics/usagereports/" + collectionVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID)) // ** THEN ** .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is( - UsageReportMatcher - .matchUsageReport(collectionVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID, - TOTAL_VISITS_PER_MONTH_REPORT_ID, expectedPoints)))); + UsageReportMatcher.matchUsageReport( + collectionVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID, + TOTAL_VISITS_PER_MONTH_REPORT_ID, + this.getListOfVisitsPerMonthsPoints(2) + ) + ))); } @Test @@ -693,10 +721,9 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .contentType(contentType)) .andExpect(status().isCreated()); - UsageReportPointDsoTotalVisitsRest expectedPoint = new UsageReportPointDsoTotalVisitsRest(); - expectedPoint.addValue("views", 1); - expectedPoint.setType("bitstream"); - expectedPoint.setId(bitstreamVisited.getID().toString()); + List expectedPoints = List.of( + getExpectedDsoViews(bitstreamVisited, 1) + ); // And request that bitstreams's TotalDownloads stat report getClient(adminToken).perform( @@ -704,9 +731,12 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes // ** THEN ** .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is( - UsageReportMatcher - .matchUsageReport(bitstreamVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID, - TOTAL_DOWNLOADS_REPORT_ID, Arrays.asList(expectedPoint))))); + UsageReportMatcher.matchUsageReport( + bitstreamVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID, + TOTAL_DOWNLOADS_REPORT_ID, + expectedPoints + ) + ))); // only admin has access to downloads report getClient(loggedInToken).perform( @@ -723,18 +753,24 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes getClient(loggedInToken).perform( get("/api/statistics/usagereports/" + bitstreamVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID)) .andExpect(status().isOk()) - .andExpect(jsonPath("$", - Matchers.is(UsageReportMatcher.matchUsageReport( - bitstreamVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID, TOTAL_DOWNLOADS_REPORT_ID, - Arrays.asList(expectedPoint))))); + .andExpect(jsonPath("$", Matchers.is( + UsageReportMatcher.matchUsageReport( + bitstreamVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID, + TOTAL_DOWNLOADS_REPORT_ID, + expectedPoints + ) + ))); getClient().perform( get("/api/statistics/usagereports/" + bitstreamVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID)) .andExpect(status().isOk()) - .andExpect(jsonPath("$", - Matchers.is(UsageReportMatcher.matchUsageReport( - bitstreamVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID, TOTAL_DOWNLOADS_REPORT_ID, - Arrays.asList(expectedPoint))))); + .andExpect(jsonPath("$", Matchers.is( + UsageReportMatcher.matchUsageReport( + bitstreamVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID, + TOTAL_DOWNLOADS_REPORT_ID, + expectedPoints + ) + ))); } @Test @@ -752,11 +788,6 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .contentType(contentType)) .andExpect(status().isCreated()); - UsageReportPointDsoTotalVisitsRest expectedPoint = new UsageReportPointDsoTotalVisitsRest(); - expectedPoint.addValue("views", 1); - expectedPoint.setId("BitstreamVisitedName"); - expectedPoint.setType("bitstream"); - // And request that item's TotalDownloads stat report getClient(adminToken).perform( get("/api/statistics/usagereports/" + itemNotVisitedWithBitstreams.getID() + "_" + @@ -764,9 +795,14 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes // ** THEN ** .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is( - UsageReportMatcher - .matchUsageReport(itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID, - TOTAL_DOWNLOADS_REPORT_ID, Arrays.asList(expectedPoint))))); + UsageReportMatcher.matchUsageReport( + itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID, + TOTAL_DOWNLOADS_REPORT_ID, + List.of( + getExpectedDsoViews(bitstreamVisited, 1) + ) + ) + ))); } @Test @@ -780,9 +816,12 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes // ** THEN ** .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is( - UsageReportMatcher - .matchUsageReport(itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID, - TOTAL_DOWNLOADS_REPORT_ID, new ArrayList<>())))); + UsageReportMatcher.matchUsageReport( + itemNotVisitedWithBitstreams.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID, + TOTAL_DOWNLOADS_REPORT_ID, + List.of() + ) + ))); } @Test @@ -810,10 +849,9 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .contentType(contentType)) .andExpect(status().isCreated()); - UsageReportPointCountryRest expectedPoint = new UsageReportPointCountryRest(); - expectedPoint.addValue("views", 1); - expectedPoint.setId("US"); - expectedPoint.setLabel("United States"); + List expectedPoints = List.of( + getExpectedCountryViews("US", "United States", 1) + ); // And request that collection's TopCountries report getClient(adminToken).perform( @@ -821,9 +859,12 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes // ** THEN ** .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is( - UsageReportMatcher - .matchUsageReport(collectionVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID, - TOP_COUNTRIES_REPORT_ID, Arrays.asList(expectedPoint))))); + UsageReportMatcher.matchUsageReport( + collectionVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID, + TOP_COUNTRIES_REPORT_ID, + expectedPoints + ) + ))); // only admin has access to countries report getClient(loggedInToken).perform( @@ -840,18 +881,24 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes getClient(loggedInToken).perform( get("/api/statistics/usagereports/" + collectionVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID)) .andExpect(status().isOk()) - .andExpect(jsonPath("$", - Matchers.is(UsageReportMatcher.matchUsageReport( - collectionVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID, TOP_COUNTRIES_REPORT_ID, - Arrays.asList(expectedPoint))))); + .andExpect(jsonPath("$", Matchers.is( + UsageReportMatcher.matchUsageReport( + collectionVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID, + TOP_COUNTRIES_REPORT_ID, + expectedPoints + ) + ))); getClient().perform( get("/api/statistics/usagereports/" + collectionVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID)) .andExpect(status().isOk()) - .andExpect(jsonPath("$", - Matchers.is(UsageReportMatcher.matchUsageReport( - collectionVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID, TOP_COUNTRIES_REPORT_ID, - Arrays.asList(expectedPoint))))); + .andExpect(jsonPath("$", Matchers.is( + UsageReportMatcher.matchUsageReport( + collectionVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID, + TOP_COUNTRIES_REPORT_ID, + expectedPoints + ) + ))); } /** @@ -877,20 +924,20 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .contentType(contentType)) .andExpect(status().isCreated()); - UsageReportPointCountryRest expectedPoint = new UsageReportPointCountryRest(); - expectedPoint.addValue("views", 2); - expectedPoint.setId("US"); - expectedPoint.setLabel("United States"); - // And request that collection's TopCountries report getClient(adminToken).perform( get("/api/statistics/usagereports/" + communityVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID)) // ** THEN ** .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is( - UsageReportMatcher - .matchUsageReport(communityVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID, - TOP_COUNTRIES_REPORT_ID, Arrays.asList(expectedPoint))))); + UsageReportMatcher.matchUsageReport( + communityVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID, + TOP_COUNTRIES_REPORT_ID, + List.of( + getExpectedCountryViews("US", "United States", 2) + ) + ) + ))); } /** @@ -906,9 +953,12 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes // ** THEN ** .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is( - UsageReportMatcher - .matchUsageReport(itemNotVisitedWithBitstreams.getID() + "_" + TOP_COUNTRIES_REPORT_ID, - TOP_COUNTRIES_REPORT_ID, new ArrayList<>())))); + UsageReportMatcher.matchUsageReport( + itemNotVisitedWithBitstreams.getID() + "_" + TOP_COUNTRIES_REPORT_ID, + TOP_COUNTRIES_REPORT_ID, + List.of() + ) + ))); } /** @@ -929,9 +979,9 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .contentType(contentType)) .andExpect(status().isCreated()); - UsageReportPointCityRest expectedPoint = new UsageReportPointCityRest(); - expectedPoint.addValue("views", 1); - expectedPoint.setId("New York"); + List expectedPoints = List.of( + getExpectedCityViews("New York", 1) + ); // And request that item's TopCities report getClient(adminToken).perform( @@ -939,9 +989,12 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes // ** THEN ** .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is( - UsageReportMatcher - .matchUsageReport(itemVisited.getID() + "_" + TOP_CITIES_REPORT_ID, - TOP_CITIES_REPORT_ID, Arrays.asList(expectedPoint))))); + UsageReportMatcher.matchUsageReport( + itemVisited.getID() + "_" + TOP_CITIES_REPORT_ID, + TOP_CITIES_REPORT_ID, + expectedPoints + ) + ))); // only admin has access to cities report getClient(loggedInToken).perform( @@ -958,18 +1011,24 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes getClient(loggedInToken).perform( get("/api/statistics/usagereports/" + itemVisited.getID() + "_" + TOP_CITIES_REPORT_ID)) .andExpect(status().isOk()) - .andExpect(jsonPath("$", - Matchers.is( - UsageReportMatcher.matchUsageReport(itemVisited.getID() + "_" + TOP_CITIES_REPORT_ID, - TOP_CITIES_REPORT_ID, Arrays.asList(expectedPoint))))); + .andExpect(jsonPath("$", Matchers.is( + UsageReportMatcher.matchUsageReport( + itemVisited.getID() + "_" + TOP_CITIES_REPORT_ID, + TOP_CITIES_REPORT_ID, + expectedPoints + ) + ))); getClient().perform( get("/api/statistics/usagereports/" + itemVisited.getID() + "_" + TOP_CITIES_REPORT_ID)) .andExpect(status().isOk()) - .andExpect(jsonPath("$", - Matchers.is( - UsageReportMatcher.matchUsageReport(itemVisited.getID() + "_" + TOP_CITIES_REPORT_ID, - TOP_CITIES_REPORT_ID, Arrays.asList(expectedPoint))))); + .andExpect(jsonPath("$", Matchers.is( + UsageReportMatcher.matchUsageReport( + itemVisited.getID() + "_" + TOP_CITIES_REPORT_ID, + TOP_CITIES_REPORT_ID, + expectedPoints + ) + ))); } /** @@ -1000,19 +1059,20 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .contentType(contentType)) .andExpect(status().isCreated()); - UsageReportPointCityRest expectedPoint = new UsageReportPointCityRest(); - expectedPoint.addValue("views", 3); - expectedPoint.setId("New York"); - // And request that community's TopCities report getClient(adminToken).perform( get("/api/statistics/usagereports/" + communityVisited.getID() + "_" + TOP_CITIES_REPORT_ID)) // ** THEN ** .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is( - UsageReportMatcher - .matchUsageReport(communityVisited.getID() + "_" + TOP_CITIES_REPORT_ID, - TOP_CITIES_REPORT_ID, Arrays.asList(expectedPoint))))); + UsageReportMatcher.matchUsageReport( + communityVisited.getID() + "_" + TOP_CITIES_REPORT_ID, + TOP_CITIES_REPORT_ID, + List.of( + getExpectedCityViews("New York", 3) + ) + ) + ))); } /** @@ -1028,9 +1088,12 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes // ** THEN ** .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is( - UsageReportMatcher - .matchUsageReport(collectionNotVisited.getID() + "_" + TOP_CITIES_REPORT_ID, - TOP_CITIES_REPORT_ID, new ArrayList<>())))); + UsageReportMatcher.matchUsageReport( + collectionNotVisited.getID() + "_" + TOP_CITIES_REPORT_ID, + TOP_CITIES_REPORT_ID, + List.of() + ) + ))); } @Test @@ -1155,18 +1218,6 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .contentType(contentType)) .andExpect(status().isCreated()); - List points = new ArrayList<>(); - UsageReportPointDsoTotalVisitsRest expectedPoint1 = new UsageReportPointDsoTotalVisitsRest(); - expectedPoint1.addValue("views", 1); - expectedPoint1.setType("item"); - expectedPoint1.setId(itemVisited.getID().toString()); - UsageReportPointDsoTotalVisitsRest expectedPoint2 = new UsageReportPointDsoTotalVisitsRest(); - expectedPoint2.addValue("views", 2); - expectedPoint2.setType("item"); - expectedPoint2.setId(itemVisited2.getID().toString()); - points.add(expectedPoint1); - points.add(expectedPoint2); - // And request the sites global usage report (show top most popular items) getClient(adminToken) .perform(get("/api/statistics/usagereports/search/object?uri=http://localhost:8080/server/api/core" + @@ -1175,8 +1226,15 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.usagereports", not(empty()))) .andExpect(jsonPath("$._embedded.usagereports", Matchers.containsInAnyOrder( - UsageReportMatcher - .matchUsageReport(site.getID() + "_" + TOTAL_VISITS_REPORT_ID, TOTAL_VISITS_REPORT_ID, points)))); + UsageReportMatcher.matchUsageReport( + site.getID() + "_" + TOTAL_VISITS_REPORT_ID, + TOTAL_VISITS_REPORT_ID, + List.of( + getExpectedDsoViews(itemVisited, 1), + getExpectedDsoViews(itemVisited2, 2) + ) + ) + ))); } @Test @@ -1194,20 +1252,6 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .contentType(contentType)) .andExpect(status().isCreated()); - UsageReportPointDsoTotalVisitsRest expectedPointTotalVisits = new UsageReportPointDsoTotalVisitsRest(); - expectedPointTotalVisits.addValue("views", 1); - expectedPointTotalVisits.setType("community"); - expectedPointTotalVisits.setId(communityVisited.getID().toString()); - - UsageReportPointCityRest expectedPointCity = new UsageReportPointCityRest(); - expectedPointCity.addValue("views", 1); - expectedPointCity.setId("New York"); - - UsageReportPointCountryRest expectedPointCountry = new UsageReportPointCountryRest(); - expectedPointCountry.addValue("views", 1); - expectedPointCountry.setId("US"); - expectedPointCountry.setLabel("United States"); - // And request the community usage reports getClient(adminToken) .perform(get("/api/statistics/usagereports/search/object?uri=http://localhost:8080/server/api/core" + @@ -1216,27 +1260,39 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.usagereports", not(empty()))) .andExpect(jsonPath("$._embedded.usagereports", Matchers.containsInAnyOrder( - UsageReportMatcher - .matchUsageReport(communityVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, TOTAL_VISITS_REPORT_ID, - Arrays.asList(expectedPointTotalVisits)), - UsageReportMatcher.matchUsageReport(communityVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID, + UsageReportMatcher.matchUsageReport( + communityVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, + TOTAL_VISITS_REPORT_ID, + List.of( + getExpectedDsoViews(communityVisited, 1) + ) + ), + UsageReportMatcher.matchUsageReport( + communityVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID, TOTAL_VISITS_PER_MONTH_REPORT_ID, - this.getListOfVisitsPerMonthsPoints(1)), - UsageReportMatcher.matchUsageReport(communityVisited.getID() + "_" + TOP_CITIES_REPORT_ID, - TOP_CITIES_REPORT_ID, Arrays.asList(expectedPointCity)), - UsageReportMatcher.matchUsageReport(communityVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID, - TOP_COUNTRIES_REPORT_ID, Arrays.asList(expectedPointCountry)) - ))); + this.getListOfVisitsPerMonthsPoints(1) + ), + UsageReportMatcher.matchUsageReport( + communityVisited.getID() + "_" + TOP_CITIES_REPORT_ID, + TOP_CITIES_REPORT_ID, + List.of( + getExpectedCityViews("New York", 1) + ) + ), + UsageReportMatcher.matchUsageReport( + communityVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID, + TOP_COUNTRIES_REPORT_ID, + List.of( + getExpectedCountryViews("US", "United States", 1) + ) + ) + ))); } @Test public void usageReportsSearch_Collection_NotVisited() throws Exception { // ** WHEN ** // Collection is not visited - UsageReportPointDsoTotalVisitsRest expectedPointTotalVisits = new UsageReportPointDsoTotalVisitsRest(); - expectedPointTotalVisits.addValue("views", 0); - expectedPointTotalVisits.setType("collection"); - expectedPointTotalVisits.setId(collectionNotVisited.getID().toString()); // And request the collection's usage reports getClient(adminToken) .perform(get("/api/statistics/usagereports/search/object?uri=http://localhost:8080/server/api/core" + @@ -1245,18 +1301,29 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.usagereports", not(empty()))) .andExpect(jsonPath("$._embedded.usagereports", Matchers.containsInAnyOrder( - UsageReportMatcher - .matchUsageReport(collectionNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, - TOTAL_VISITS_REPORT_ID, - Arrays.asList(expectedPointTotalVisits)), - UsageReportMatcher - .matchUsageReport(collectionNotVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID, - TOTAL_VISITS_PER_MONTH_REPORT_ID, - this.getListOfVisitsPerMonthsPoints(0)), - UsageReportMatcher.matchUsageReport(collectionNotVisited.getID() + "_" + TOP_CITIES_REPORT_ID, - TOP_CITIES_REPORT_ID, new ArrayList<>()), - UsageReportMatcher.matchUsageReport(collectionNotVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID, - TOP_COUNTRIES_REPORT_ID, new ArrayList<>())))); + UsageReportMatcher.matchUsageReport( + collectionNotVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, + TOTAL_VISITS_REPORT_ID, + List.of( + getExpectedDsoViews(collectionNotVisited, 0) + ) + ), + UsageReportMatcher.matchUsageReport( + collectionNotVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID, + TOTAL_VISITS_PER_MONTH_REPORT_ID, + this.getListOfVisitsPerMonthsPoints(0) + ), + UsageReportMatcher.matchUsageReport( + collectionNotVisited.getID() + "_" + TOP_CITIES_REPORT_ID, + TOP_CITIES_REPORT_ID, + List.of() + ), + UsageReportMatcher.matchUsageReport( + collectionNotVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID, + TOP_COUNTRIES_REPORT_ID, + List.of() + ) + ))); } @Test @@ -1274,20 +1341,6 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .contentType(contentType)) .andExpect(status().isCreated()); - UsageReportPointDsoTotalVisitsRest expectedPointTotalVisits = new UsageReportPointDsoTotalVisitsRest(); - expectedPointTotalVisits.addValue("views", 1); - expectedPointTotalVisits.setType("item"); - expectedPointTotalVisits.setId(itemVisited.getID().toString()); - - UsageReportPointCityRest expectedPointCity = new UsageReportPointCityRest(); - expectedPointCity.addValue("views", 1); - expectedPointCity.setId("New York"); - - UsageReportPointCountryRest expectedPointCountry = new UsageReportPointCountryRest(); - expectedPointCountry.addValue("views", 1); - expectedPointCountry.setId("US"); - expectedPointCountry.setLabel("United States"); - // And request the community usage reports getClient(adminToken) .perform(get("/api/statistics/usagereports/search/object?uri=http://localhost:8080/server/api/core" + @@ -1296,18 +1349,38 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.usagereports", not(empty()))) .andExpect(jsonPath("$._embedded.usagereports", Matchers.containsInAnyOrder( - UsageReportMatcher - .matchUsageReport(itemVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, TOTAL_VISITS_REPORT_ID, - Arrays.asList(expectedPointTotalVisits)), - UsageReportMatcher.matchUsageReport(itemVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID, + UsageReportMatcher.matchUsageReport( + itemVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, + TOTAL_VISITS_REPORT_ID, + List.of( + getExpectedDsoViews(itemVisited, 1) + ) + ), + UsageReportMatcher.matchUsageReport( + itemVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID, TOTAL_VISITS_PER_MONTH_REPORT_ID, - this.getListOfVisitsPerMonthsPoints(1)), - UsageReportMatcher.matchUsageReport(itemVisited.getID() + "_" + TOP_CITIES_REPORT_ID, - TOP_CITIES_REPORT_ID, Arrays.asList(expectedPointCity)), - UsageReportMatcher.matchUsageReport(itemVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID, - TOP_COUNTRIES_REPORT_ID, Arrays.asList(expectedPointCountry)), - UsageReportMatcher.matchUsageReport(itemVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID, - TOTAL_DOWNLOADS_REPORT_ID, new ArrayList<>())))); + this.getListOfVisitsPerMonthsPoints(1) + ), + UsageReportMatcher.matchUsageReport( + itemVisited.getID() + "_" + TOP_CITIES_REPORT_ID, + TOP_CITIES_REPORT_ID, + List.of( + getExpectedCityViews("New York", 1) + ) + ), + UsageReportMatcher.matchUsageReport( + itemVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID, + TOP_COUNTRIES_REPORT_ID, + List.of( + getExpectedCountryViews("US", "United States", 1) + ) + ), + UsageReportMatcher.matchUsageReport( + itemVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID, + TOTAL_DOWNLOADS_REPORT_ID, + List.of() + ) + ))); } @Test @@ -1355,32 +1428,6 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .contentType(contentType)) .andExpect(status().isCreated()); - UsageReportPointDsoTotalVisitsRest expectedPointTotalVisits = new UsageReportPointDsoTotalVisitsRest(); - expectedPointTotalVisits.addValue("views", 1); - expectedPointTotalVisits.setType("item"); - expectedPointTotalVisits.setId(itemVisited.getID().toString()); - - UsageReportPointCityRest expectedPointCity = new UsageReportPointCityRest(); - expectedPointCity.addValue("views", 1); - expectedPointCity.setId("New York"); - - UsageReportPointCountryRest expectedPointCountry = new UsageReportPointCountryRest(); - expectedPointCountry.addValue("views", 1); - expectedPointCountry.setId("US"); - expectedPointCountry.setLabel("United States"); - - List totalDownloadsPoints = new ArrayList<>(); - UsageReportPointDsoTotalVisitsRest expectedPointTotalVisitsBit1 = new UsageReportPointDsoTotalVisitsRest(); - expectedPointTotalVisitsBit1.addValue("views", 1); - expectedPointTotalVisitsBit1.setId("bitstream1"); - expectedPointTotalVisitsBit1.setType("bitstream"); - UsageReportPointDsoTotalVisitsRest expectedPointTotalVisitsBit2 = new UsageReportPointDsoTotalVisitsRest(); - expectedPointTotalVisitsBit2.addValue("views", 2); - expectedPointTotalVisitsBit2.setId("bitstream2"); - expectedPointTotalVisitsBit2.setType("bitstream"); - totalDownloadsPoints.add(expectedPointTotalVisitsBit1); - totalDownloadsPoints.add(expectedPointTotalVisitsBit2); - // And request the community usage reports getClient(adminToken) .perform(get("/api/statistics/usagereports/search/object?uri=http://localhost:8080/server/api/core" + @@ -1389,18 +1436,41 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.usagereports", not(empty()))) .andExpect(jsonPath("$._embedded.usagereports", Matchers.containsInAnyOrder( - UsageReportMatcher - .matchUsageReport(itemVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, TOTAL_VISITS_REPORT_ID, - Arrays.asList(expectedPointTotalVisits)), - UsageReportMatcher.matchUsageReport(itemVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID, + UsageReportMatcher.matchUsageReport( + itemVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, + TOTAL_VISITS_REPORT_ID, + List.of( + getExpectedDsoViews(itemVisited, 1) + ) + ), + UsageReportMatcher.matchUsageReport( + itemVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID, TOTAL_VISITS_PER_MONTH_REPORT_ID, - this.getListOfVisitsPerMonthsPoints(1)), - UsageReportMatcher.matchUsageReport(itemVisited.getID() + "_" + TOP_CITIES_REPORT_ID, - TOP_CITIES_REPORT_ID, Arrays.asList(expectedPointCity)), - UsageReportMatcher.matchUsageReport(itemVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID, - TOP_COUNTRIES_REPORT_ID, Arrays.asList(expectedPointCountry)), - UsageReportMatcher.matchUsageReport(itemVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID, - TOTAL_DOWNLOADS_REPORT_ID, totalDownloadsPoints)))); + getListOfVisitsPerMonthsPoints(1) + ), + UsageReportMatcher.matchUsageReport( + itemVisited.getID() + "_" + TOP_CITIES_REPORT_ID, + TOP_CITIES_REPORT_ID, + List.of( + getExpectedCityViews("New York", 1) + ) + ), + UsageReportMatcher.matchUsageReport( + itemVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID, + TOP_COUNTRIES_REPORT_ID, + List.of( + getExpectedCountryViews("US", "United States", 1) + ) + ), + UsageReportMatcher.matchUsageReport( + itemVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID, + TOTAL_DOWNLOADS_REPORT_ID, + List.of( + getExpectedDsoViews(bitstream1, 1), + getExpectedDsoViews(bitstream2, 2) + ) + ) + ))); } @Test @@ -1418,20 +1488,9 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .contentType(contentType)) .andExpect(status().isCreated()); - UsageReportPointDsoTotalVisitsRest expectedPointTotalVisits = new UsageReportPointDsoTotalVisitsRest(); - expectedPointTotalVisits.addValue("views", 1); - expectedPointTotalVisits.setType("bitstream"); - expectedPointTotalVisits.setLabel("BitstreamVisitedName"); - expectedPointTotalVisits.setId(bitstreamVisited.getID().toString()); - - UsageReportPointCityRest expectedPointCity = new UsageReportPointCityRest(); - expectedPointCity.addValue("views", 1); - expectedPointCity.setId("New York"); - - UsageReportPointCountryRest expectedPointCountry = new UsageReportPointCountryRest(); - expectedPointCountry.addValue("views", 1); - expectedPointCountry.setId("US"); - expectedPointCountry.setLabel("United States"); + List expectedTotalVisits = List.of( + getExpectedDsoViews(bitstreamVisited, 1) + ); // And request the community usage reports getClient(adminToken) @@ -1441,18 +1500,36 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.usagereports", not(empty()))) .andExpect(jsonPath("$._embedded.usagereports", Matchers.containsInAnyOrder( - UsageReportMatcher - .matchUsageReport(bitstreamVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, TOTAL_VISITS_REPORT_ID, - Arrays.asList(expectedPointTotalVisits)), - UsageReportMatcher.matchUsageReport(bitstreamVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID, + UsageReportMatcher.matchUsageReport( + bitstreamVisited.getID() + "_" + TOTAL_VISITS_REPORT_ID, + TOTAL_VISITS_REPORT_ID, + expectedTotalVisits + ), + UsageReportMatcher.matchUsageReport( + bitstreamVisited.getID() + "_" + TOTAL_VISITS_PER_MONTH_REPORT_ID, TOTAL_VISITS_PER_MONTH_REPORT_ID, - this.getListOfVisitsPerMonthsPoints(1)), - UsageReportMatcher.matchUsageReport(bitstreamVisited.getID() + "_" + TOP_CITIES_REPORT_ID, - TOP_CITIES_REPORT_ID, Arrays.asList(expectedPointCity)), - UsageReportMatcher.matchUsageReport(bitstreamVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID, - TOP_COUNTRIES_REPORT_ID, Arrays.asList(expectedPointCountry)), - UsageReportMatcher.matchUsageReport(bitstreamVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID, - TOTAL_DOWNLOADS_REPORT_ID, Arrays.asList(expectedPointTotalVisits))))); + this.getListOfVisitsPerMonthsPoints(1) + ), + UsageReportMatcher.matchUsageReport( + bitstreamVisited.getID() + "_" + TOP_CITIES_REPORT_ID, + TOP_CITIES_REPORT_ID, + List.of( + getExpectedCityViews("New York", 1) + ) + ), + UsageReportMatcher.matchUsageReport( + bitstreamVisited.getID() + "_" + TOP_COUNTRIES_REPORT_ID, + TOP_COUNTRIES_REPORT_ID, + List.of( + getExpectedCountryViews("US", "United States", 1) + ) + ), + UsageReportMatcher.matchUsageReport( + bitstreamVisited.getID() + "_" + TOTAL_DOWNLOADS_REPORT_ID, + TOTAL_DOWNLOADS_REPORT_ID, + expectedTotalVisits + ) + ))); } // Create expected points from -6 months to now, with given number of views in current month @@ -1475,4 +1552,34 @@ public class StatisticsRestRepositoryIT extends AbstractControllerIntegrationTes } return expectedPoints; } + + private UsageReportPointDsoTotalVisitsRest getExpectedDsoViews(DSpaceObject dso, int views) { + UsageReportPointDsoTotalVisitsRest point = new UsageReportPointDsoTotalVisitsRest(); + + point.addValue("views", views); + point.setType(StringUtils.lowerCase(Constants.typeText[dso.getType()])); + point.setId(dso.getID().toString()); + point.setLabel(dso.getName()); + + return point; + } + + private UsageReportPointCountryRest getExpectedCountryViews(String id, String label, int views) { + UsageReportPointCountryRest point = new UsageReportPointCountryRest(); + + point.addValue("views", views); + point.setId(id); + point.setLabel(label); + + return point; + } + + private UsageReportPointCityRest getExpectedCityViews(String id, int views) { + UsageReportPointCityRest point = new UsageReportPointCityRest(); + + point.addValue("views", views); + point.setId(id); + + return point; + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index 0f9d8f2bb9..4a9f03ffff 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java @@ -995,6 +995,305 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration bibtex.close(); } + @Test + /** + * Test the creation of workspaceitems POSTing to the resource collection endpoint a bibtex file + * + * @throws Exception + */ + public void createSingleWorkspaceItemFromBibtexArticleFileWithOneEntryTest() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .build(); + Collection col2 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 2") + .withSubmitterGroup(eperson) + .build(); + + InputStream bibtex = getClass().getResourceAsStream("bibtex-test-article.bib"); + final MockMultipartFile bibtexFile = new MockMultipartFile("file", "/local/path/bibtex-test-article.bib", + "application/x-bibtex", bibtex); + + context.restoreAuthSystemState(); + + AtomicReference> idRef = new AtomicReference<>(); + String authToken = getAuthToken(eperson.getEmail(), password); + try { + // create a workspaceitem from a single bibliographic entry file explicitly in the default collection (col1) + getClient(authToken).perform(multipart("/api/submission/workspaceitems") + .file(bibtexFile)) + // create should return 200, 201 (created) is better for single resource + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.workspaceitems[0]" + + ".sections.traditionalpageone['dc.title'][0].value", + is("My Article"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0]" + + ".sections.traditionalpageone['dc.type'][0].value", + is("article"))) + .andExpect( + jsonPath("$._embedded.workspaceitems[0]._embedded.collection.id", + is(col1.getID().toString()))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]" + + ".metadata['dc.source'][0].value", + is("/local/path/bibtex-test-article.bib"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]" + + ".metadata['dc.title'][0].value", + is("bibtex-test-article.bib"))) + .andExpect( + jsonPath("$._embedded.workspaceitems[*]._embedded.upload").doesNotExist()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), + "$._embedded.workspaceitems[*].id"))); + } finally { + if (idRef != null && idRef.get() != null) { + for (int i : idRef.get()) { + WorkspaceItemBuilder.deleteWorkspaceItem(i); + } + } + } + + // create a workspaceitem from a single bibliographic entry file explicitly in the col2 + try { + getClient(authToken).perform(multipart("/api/submission/workspaceitems") + .file(bibtexFile) + .param("owningCollection", col2.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.workspaceitems[0]" + + ".sections.traditionalpageone['dc.title'][0].value", + is("My Article"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0]" + + ".sections.traditionalpageone['dc.type'][0].value", + is("article"))) + .andExpect( + jsonPath("$._embedded.workspaceitems[0]._embedded.collection.id", + is(col2.getID().toString()))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]" + + ".metadata['dc.source'][0].value", + is("/local/path/bibtex-test-article.bib"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload" + + ".files[0].metadata['dc.title'][0].value", + is("bibtex-test-article.bib"))) + .andExpect( + jsonPath("$._embedded.workspaceitems[*]._embedded.upload").doesNotExist()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), + "$._embedded.workspaceitems[*].id"))); + } finally { + if (idRef != null && idRef.get() != null) { + for (int i : idRef.get()) { + WorkspaceItemBuilder.deleteWorkspaceItem(i); + } + } + } + bibtex.close(); + } + + @Test + public void createSingleWorkspaceItemFromBibtexFileWithDiacriticsTest() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .build(); + Collection col2 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 2") + .withSubmitterGroup(eperson) + .build(); + + InputStream bibtex = getClass().getResourceAsStream("bibtex-test-diacritics.bib"); + final MockMultipartFile bibtexFile = new MockMultipartFile("file", "/local/path/bibtex-test-diacritics.bib", + "application/x-bibtex", bibtex); + + context.restoreAuthSystemState(); + + AtomicReference> idRef = new AtomicReference<>(); + String authToken = getAuthToken(eperson.getEmail(), password); + try { + // create a workspaceitem from a single bibliographic entry file explicitly in the default collection (col1) + getClient(authToken).perform(multipart("/api/submission/workspaceitems") + .file(bibtexFile)) + // create should return 200, 201 (created) is better for single resource + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections." + + "traditionalpageone['dc.title'][0].value", + is("The German umlauts: ÄÖüß"))) + .andExpect( + jsonPath("$._embedded.workspaceitems[0]._embedded.collection.id", + is(col1.getID().toString()))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]" + + ".metadata['dc.source'][0].value", + is("/local/path/bibtex-test-diacritics.bib"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]" + + ".metadata['dc.title'][0].value", + is("bibtex-test-diacritics.bib"))) + .andExpect( + jsonPath("$._embedded.workspaceitems[*]._embedded.upload").doesNotExist()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), + "$._embedded.workspaceitems[*].id"))); + } finally { + if (idRef != null && idRef.get() != null) { + for (int i : idRef.get()) { + WorkspaceItemBuilder.deleteWorkspaceItem(i); + } + } + } + + // create a workspaceitem from a single bibliographic entry file explicitly in the col2 + try { + getClient(authToken).perform(multipart("/api/submission/workspaceitems") + .file(bibtexFile) + .param("owningCollection", col2.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections." + + "traditionalpageone['dc.title'][0].value", + is("The German umlauts: ÄÖüß"))) + .andExpect( + jsonPath("$._embedded.workspaceitems[0]._embedded.collection.id", + is(col2.getID().toString()))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]" + + ".metadata['dc.source'][0].value", + is("/local/path/bibtex-test-diacritics.bib"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload" + + ".files[0].metadata['dc.title'][0].value", + is("bibtex-test-diacritics.bib"))) + .andExpect( + jsonPath("$._embedded.workspaceitems[*]._embedded.upload").doesNotExist()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), + "$._embedded.workspaceitems[*].id"))); + } finally { + if (idRef != null && idRef.get() != null) { + for (int i : idRef.get()) { + WorkspaceItemBuilder.deleteWorkspaceItem(i); + } + } + } + bibtex.close(); + } + + @Test + /** + * Test the creation of workspaceitems POSTing to the resource collection endpoint a bibtex file + * + * @throws Exception + */ + public void createSingleWorkspaceItemFromBibtexFileWithMultipleAuthorsTest() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 1") + .withSubmitterGroup(eperson) + .build(); + Collection col2 = CollectionBuilder.createCollection(context, child1) + .withName("Collection 2") + .withSubmitterGroup(eperson) + .build(); + + InputStream bibtex = getClass().getResourceAsStream("bibtex-test-multiple-authors.bib"); + final MockMultipartFile bibtexFile = new MockMultipartFile("file", + "/local/path/bibtex-test-multiple-authors.bib", + "application/x-bibtex", bibtex); + + context.restoreAuthSystemState(); + + AtomicReference> idRef = new AtomicReference<>(); + String authToken = getAuthToken(eperson.getEmail(), password); + try { + // create a workspaceitem from a single bibliographic entry file explicitly in the default collection (col1) + getClient(authToken).perform(multipart("/api/submission/workspaceitems") + .file(bibtexFile)) + // create should return 200, 201 (created) is better for single resource + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.workspaceitems[0]" + + ".sections.traditionalpageone['dc.title'][0].value", + is("My Article"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0]" + + ".sections.traditionalpageone['dc.contributor.author'][0].value", + is("A. Nauthor"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0]" + + ".sections.traditionalpageone['dc.contributor.author'][1].value", + is("A. Nother"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0]" + + ".sections.traditionalpageone['dc.contributor.author'][2].value", + is("A. Third"))) + .andExpect( + jsonPath("$._embedded.workspaceitems[0]._embedded.collection.id", + is(col1.getID().toString()))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]" + + ".metadata['dc.source'][0].value", + is("/local/path/bibtex-test-multiple-authors.bib"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]" + + ".metadata['dc.title'][0].value", + is("bibtex-test-multiple-authors.bib"))) + .andExpect( + jsonPath("$._embedded.workspaceitems[*]._embedded.upload").doesNotExist()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), + "$._embedded.workspaceitems[*].id"))); + } finally { + if (idRef != null && idRef.get() != null) { + for (int i : idRef.get()) { + WorkspaceItemBuilder.deleteWorkspaceItem(i); + } + } + } + + // create a workspaceitem from a single bibliographic entry file explicitly in the col2 + try { + getClient(authToken).perform(multipart("/api/submission/workspaceitems") + .file(bibtexFile) + .param("owningCollection", col2.getID().toString())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.workspaceitems[0]" + + ".sections.traditionalpageone['dc.title'][0].value", + is("My Article"))) + .andExpect( + jsonPath("$._embedded.workspaceitems[0]._embedded.collection.id", + is(col2.getID().toString()))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload.files[0]" + + ".metadata['dc.source'][0].value", + is("/local/path/bibtex-test-multiple-authors.bib"))) + .andExpect(jsonPath("$._embedded.workspaceitems[0].sections.upload" + + ".files[0].metadata['dc.title'][0].value", + is("bibtex-test-multiple-authors.bib"))) + .andExpect( + jsonPath("$._embedded.workspaceitems[*]._embedded.upload").doesNotExist()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), + "$._embedded.workspaceitems[*].id"))); + } finally { + if (idRef != null && idRef.get() != null) { + for (int i : idRef.get()) { + WorkspaceItemBuilder.deleteWorkspaceItem(i); + } + } + } + bibtex.close(); + } + @Test /** * Test the creation of workspaceitems POSTing to the resource collection endpoint a csv file diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/eperson/DeleteEPersonSubmitterIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/eperson/DeleteEPersonSubmitterIT.java index fb8b44fa17..e020c04b1a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/eperson/DeleteEPersonSubmitterIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/eperson/DeleteEPersonSubmitterIT.java @@ -142,10 +142,10 @@ public class DeleteEPersonSubmitterIT extends AbstractControllerIntegrationTest Item item = itemService.find(context, installItem.getID()); - RequestItemAuthor requestItemAuthor = requestItemAuthorExtractor.getRequestItemAuthor(context, item); + List requestItemAuthor = requestItemAuthorExtractor.getRequestItemAuthor(context, item); - assertEquals("Help Desk", requestItemAuthor.getFullName()); - assertEquals("dspace-help@myu.edu", requestItemAuthor.getEmail()); + assertEquals("Help Desk", requestItemAuthor.get(0).getFullName()); + assertEquals("dspace-help@myu.edu", requestItemAuthor.get(0).getEmail()); } /** @@ -171,7 +171,7 @@ public class DeleteEPersonSubmitterIT extends AbstractControllerIntegrationTest Item item = installItemService.installItem(context, wsi); - List opsToWithDraw = new ArrayList(); + List opsToWithDraw = new ArrayList<>(); ReplaceOperation replaceOperationToWithDraw = new ReplaceOperation("/withdrawn", true); opsToWithDraw.add(replaceOperationToWithDraw); String patchBodyToWithdraw = getPatchContent(opsToWithDraw); @@ -191,7 +191,7 @@ public class DeleteEPersonSubmitterIT extends AbstractControllerIntegrationTest assertNull(retrieveItemSubmitter(item.getID())); - List opsToReinstate = new ArrayList(); + List opsToReinstate = new ArrayList<>(); ReplaceOperation replaceOperationToReinstate = new ReplaceOperation("/withdrawn", false); opsToReinstate.add(replaceOperationToReinstate); String patchBodyToReinstate = getPatchContent(opsToReinstate); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/UsageReportMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/UsageReportMatcher.java index ba82d69b40..9ad1dce28a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/UsageReportMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/UsageReportMatcher.java @@ -49,13 +49,18 @@ public class UsageReportMatcher { * @param points List of points to match to the json of UsageReport's list of points * @return The matcher */ - public static Matcher matchUsageReport(String id, String reportType, - List points) { + public static Matcher matchUsageReport( + String id, String reportType, List points + ) { return allOf( matchUsageReport(id, reportType), hasJsonPath("$.points", Matchers.containsInAnyOrder( - points.stream().map(point -> UsageReportPointMatcher - .matchUsageReportPoint(point.getId(), point.getType(), point.getValues().get("views"))) - .collect(Collectors.toList())))); + points.stream() + .map(point -> UsageReportPointMatcher.matchUsageReportPoint( + point.getId(), point.getLabel(), point.getType(), point.getValues().get("views") + )) + .collect(Collectors.toList())) + ) + ); } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/UsageReportPointMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/UsageReportPointMatcher.java index e7f22066e5..f1c641b18a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/UsageReportPointMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/UsageReportPointMatcher.java @@ -28,15 +28,17 @@ public class UsageReportPointMatcher { * Matcher for the usage report points (see {@link UsageReportPointRest}) * * @param id Id to match if of json of UsageReportPoint + * @param label Label to match if of json of UsageReportPoint * @param type Type to match if of json of UsageReportPoint * @param views Nr of views, is in the values key-value pair of values of UsageReportPoint with key "views" * @return The matcher */ - public static Matcher matchUsageReportPoint(String id, String type, int views) { + public static Matcher matchUsageReportPoint(String id, String label, String type, int views) { return allOf( hasJsonPath("$.id", is(id)), + hasJsonPath("$.label", is(label)), hasJsonPath("$.type", is(type)), hasJsonPath("$.values.views", is(views)) - ); + ); } } diff --git a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/bibtex-test-article.bib b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/bibtex-test-article.bib new file mode 100644 index 0000000000..e3f1ca1d4e --- /dev/null +++ b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/bibtex-test-article.bib @@ -0,0 +1,4 @@ +@article{ Nobody01, + author = "Nobody Jr", + title = "My Article", + year = "2006" } diff --git a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/bibtex-test-diacritics.bib b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/bibtex-test-diacritics.bib new file mode 100644 index 0000000000..d1cb431bef --- /dev/null +++ b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/bibtex-test-diacritics.bib @@ -0,0 +1,4 @@ +@misc{ Nobody01, + author = "Mo\ss", + title = { The German umlauts: \"A{\"O}{\"{u}}\ss }, + year = "2006" } diff --git a/dspace-server-webapp/src/test/resources/org/dspace/app/rest/bibtex-test-multiple-authors.bib b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/bibtex-test-multiple-authors.bib new file mode 100644 index 0000000000..1db78de377 --- /dev/null +++ b/dspace-server-webapp/src/test/resources/org/dspace/app/rest/bibtex-test-multiple-authors.bib @@ -0,0 +1,4 @@ +@misc{ Nobody01, + author = "A. Nauthor and A. Nother and A. Third", + title = "My Article", + year = "2006" } diff --git a/dspace/config/spring/api/access-conditions.xml b/dspace/config/spring/api/access-conditions.xml index cd1550f389..828b31d425 100644 --- a/dspace/config/spring/api/access-conditions.xml +++ b/dspace/config/spring/api/access-conditions.xml @@ -3,10 +3,9 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> - + - - + @@ -18,7 +17,7 @@ - + @@ -31,26 +30,25 @@ - + - - + - + diff --git a/dspace/config/spring/api/bibtex-integration.xml b/dspace/config/spring/api/bibtex-integration.xml index eeabace1c7..bc5adf5886 100644 --- a/dspace/config/spring/api/bibtex-integration.xml +++ b/dspace/config/spring/api/bibtex-integration.xml @@ -17,6 +17,7 @@ only matters here for postprocessing of the value. The mapped MetadatumContributor has full control over what metadatafield is generated. + @@ -39,14 +40,24 @@ - - - + + + + + + + + - - \ No newline at end of file + + + + + + + diff --git a/dspace/config/spring/api/dublicore-metadata-mapper.xml b/dspace/config/spring/api/dublicore-metadata-mapper.xml index bb52e99b82..82c0dd47dd 100644 --- a/dspace/config/spring/api/dublicore-metadata-mapper.xml +++ b/dspace/config/spring/api/dublicore-metadata-mapper.xml @@ -25,6 +25,9 @@ + + + diff --git a/dspace/config/spring/api/external-openaire.xml b/dspace/config/spring/api/external-openaire.xml index f483ce7210..25a23a1739 100644 --- a/dspace/config/spring/api/external-openaire.xml +++ b/dspace/config/spring/api/external-openaire.xml @@ -1,6 +1,10 @@ - @@ -10,17 +14,65 @@ - + + + Project - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dspace/config/spring/api/requestitem.xml b/dspace/config/spring/api/requestitem.xml index cc18c7916f..90c49156d5 100644 --- a/dspace/config/spring/api/requestitem.xml +++ b/dspace/config/spring/api/requestitem.xml @@ -8,25 +8,65 @@ http://www.springframework.org/schema/context/spring-context-2.5.xsd" default-autowire-candidates="*Service,*DAO,javax.sql.DataSource"> + + Strategies for determining who receives Request Copy emails. + A copy request "strategy" class produces a list of addresses to which a + request email should be sent. Each strategy gets its addresses from a + different source. Select the one that meets your need, or use the + CombiningRequestItemStrategy to meld the lists from two or more other + strategies. + + - - + - - - --> + + + + - + + + + + + diff --git a/pom.xml b/pom.xml index 57aa5c5e60..42d2635dad 100644 --- a/pom.xml +++ b/pom.xml @@ -1575,18 +1575,11 @@ ojdbc6 11.2.0.4.0 - + org.dspace oclc-harvester2 - 0.1.12 - - - - xalan - xalan - 2.7.2 + 1.0.0 org.apache.httpcomponents