Merge branch 'main' into dynamic-default-dims

This commit is contained in:
Michael Spalti
2022-01-29 10:46:40 -08:00
23 changed files with 918 additions and 105 deletions

View File

@@ -7,6 +7,13 @@
*/
package org.dspace.app.itemimport;
import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_HEIGHT_QUALIFIER;
import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_IMAGE_ELEMENT;
import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_LABEL_ELEMENT;
import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_SCHEMA;
import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_TOC_ELEMENT;
import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_WIDTH_QUALIFIER;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
@@ -1172,6 +1179,59 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
boolean bundleExists = false;
boolean permissionsExist = false;
boolean descriptionExists = false;
boolean labelExists = false;
boolean heightExists = false;
boolean widthExists = false;
boolean tocExists = false;
// look for label
String labelMarker = "\tiiif-label";
int lMarkerIndex = line.indexOf(labelMarker);
int lEndIndex = 0;
if (lMarkerIndex > 0) {
lEndIndex = line.indexOf("\t", lMarkerIndex + 1);
if (lEndIndex == -1) {
lEndIndex = line.length();
}
labelExists = true;
}
// look for height
String heightMarker = "\tiiif-height";
int hMarkerIndex = line.indexOf(heightMarker);
int hEndIndex = 0;
if (hMarkerIndex > 0) {
hEndIndex = line.indexOf("\t", hMarkerIndex + 1);
if (hEndIndex == -1) {
hEndIndex = line.length();
}
heightExists = true;
}
// look for width
String widthMarker = "\tiiif-width";
int wMarkerIndex = line.indexOf(widthMarker);
int wEndIndex = 0;
if (wMarkerIndex > 0) {
wEndIndex = line.indexOf("\t", wMarkerIndex + 1);
if (wEndIndex == -1) {
wEndIndex = line.length();
}
widthExists = true;
}
// look for toc
String tocMarker = "\tiiif-toc";
int tMarkerIndex = line.indexOf(tocMarker);
int tEndIndex = 0;
if (tMarkerIndex > 0) {
tEndIndex = line.indexOf("\t", tMarkerIndex + 1);
if (tEndIndex == -1) {
tEndIndex = line.length();
}
tocExists = true;
}
// look for a bundle name
String bundleMarker = "\tbundle:";
@@ -1231,7 +1291,9 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
System.out.println("\tBitstream: " + bitstreamName + primaryStr);
}
if (permissionsExist || descriptionExists) {
if (permissionsExist || descriptionExists || labelExists || heightExists
|| widthExists || tocExists) {
System.out.println("Gathering options.");
String extraInfo = bitstreamName;
if (permissionsExist) {
@@ -1244,6 +1306,26 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
+ line.substring(dMarkerIndex, dEndIndex);
}
if (labelExists) {
extraInfo = extraInfo
+ line.substring(lMarkerIndex, lEndIndex);
}
if (heightExists) {
extraInfo = extraInfo
+ line.substring(hMarkerIndex, hEndIndex);
}
if (widthExists) {
extraInfo = extraInfo
+ line.substring(wMarkerIndex, wEndIndex);
}
if (tocExists) {
extraInfo = extraInfo
+ line.substring(tMarkerIndex, tEndIndex);
}
options.add(extraInfo);
}
}
@@ -1425,11 +1507,16 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
*/
protected void processOptions(Context c, Item myItem, List<String> options)
throws SQLException, AuthorizeException {
System.out.println("Processing options.");
for (String line : options) {
System.out.println("\tprocessing " + line);
boolean permissionsExist = false;
boolean descriptionExists = false;
boolean labelExists = false;
boolean heightExists = false;
boolean widthExists = false;
boolean tocExists = false;
String permissionsMarker = "\tpermissions:";
int pMarkerIndex = line.indexOf(permissionsMarker);
@@ -1453,6 +1540,56 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
descriptionExists = true;
}
// look for label
String labelMarker = "\tiiif-label:";
int lMarkerIndex = line.indexOf(labelMarker);
int lEndIndex = 0;
if (lMarkerIndex > 0) {
lEndIndex = line.indexOf("\t", lMarkerIndex + 1);
if (lEndIndex == -1) {
lEndIndex = line.length();
}
labelExists = true;
}
// look for height
String heightMarker = "\tiiif-height:";
int hMarkerIndex = line.indexOf(heightMarker);
int hEndIndex = 0;
if (hMarkerIndex > 0) {
hEndIndex = line.indexOf("\t", hMarkerIndex + 1);
if (hEndIndex == -1) {
hEndIndex = line.length();
}
heightExists = true;
}
// look for width
String widthMarker = "\tiiif-width:";
int wMarkerIndex = line.indexOf(widthMarker);
int wEndIndex = 0;
if (wMarkerIndex > 0) {
wEndIndex = line.indexOf("\t", wMarkerIndex + 1);
if (wEndIndex == -1) {
wEndIndex = line.length();
}
widthExists = true;
}
// look for toc
String tocMarker = "\tiiif-toc:";
int tMarkerIndex = line.indexOf(tocMarker);
int tEndIndex = 0;
if (tMarkerIndex > 0) {
tEndIndex = line.indexOf("\t", tMarkerIndex + 1);
if (tEndIndex == -1) {
tEndIndex = line.length();
}
tocExists = true;
}
int bsEndIndex = line.indexOf("\t");
String bitstreamName = line.substring(0, bsEndIndex);
@@ -1502,8 +1639,38 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
.trim();
}
String thisLabel = "";
if (labelExists) {
thisLabel = line.substring(
lMarkerIndex + labelMarker.length(), lEndIndex)
.trim();
}
String thisHeight = "";
if (heightExists) {
thisHeight = line.substring(
hMarkerIndex + heightMarker.length(), hEndIndex)
.trim();
}
String thisWidth = "";
if (widthExists) {
thisWidth = line.substring(
wMarkerIndex + widthMarker.length(), wEndIndex)
.trim();
}
String thisToc = "";
if (tocExists) {
thisToc = line.substring(
tMarkerIndex + tocMarker.length(), tEndIndex)
.trim();
}
Bitstream bs = null;
boolean notfound = true;
boolean updateRequired = false;
if (!isTest) {
// find bitstream
List<Bitstream> bitstreams = itemService.getNonInternalBitstreams(c, myItem);
@@ -1538,6 +1705,45 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
System.out.println("\tSetting description for "
+ bitstreamName);
bs.setDescription(c, thisDescription);
updateRequired = true;
}
if (labelExists) {
MetadataField metadataField = metadataFieldService
.findByElement(c, METADATA_IIIF_SCHEMA, METADATA_IIIF_LABEL_ELEMENT, null);
System.out.println("\tSetting label to " + thisLabel + " in element "
+ metadataField.getElement() + " on " + bitstreamName);
bitstreamService.addMetadata(c, bs, metadataField, null, thisLabel);
updateRequired = true;
}
if (heightExists) {
MetadataField metadataField = metadataFieldService
.findByElement(c, METADATA_IIIF_SCHEMA, METADATA_IIIF_IMAGE_ELEMENT,
METADATA_IIIF_HEIGHT_QUALIFIER);
System.out.println("\tSetting height to " + thisHeight + " in element "
+ metadataField.getElement() + " on " + bitstreamName);
bitstreamService.addMetadata(c, bs, metadataField, null, thisHeight);
updateRequired = true;
}
if (widthExists) {
MetadataField metadataField = metadataFieldService
.findByElement(c, METADATA_IIIF_SCHEMA, METADATA_IIIF_IMAGE_ELEMENT,
METADATA_IIIF_WIDTH_QUALIFIER);
System.out.println("\tSetting width to " + thisWidth + " in element "
+ metadataField.getElement() + " on " + bitstreamName);
bitstreamService.addMetadata(c, bs, metadataField, null, thisWidth);
updateRequired = true;
}
if (tocExists) {
MetadataField metadataField = metadataFieldService
.findByElement(c, METADATA_IIIF_SCHEMA, METADATA_IIIF_TOC_ELEMENT, null);
System.out.println("\tSetting toc to " + thisToc + " in element "
+ metadataField.getElement() + " on " + bitstreamName);
bitstreamService.addMetadata(c, bs, metadataField, null, thisToc);
updateRequired = true;
}
if (updateRequired) {
bitstreamService.update(c, bs);
}
}

View File

@@ -0,0 +1,50 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.content;
import java.io.IOException;
import java.util.Date;
import java.util.Objects;
import javax.mail.MessagingException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.dspace.content.service.FeedbackService;
import org.dspace.core.Context;
import org.dspace.core.Email;
import org.dspace.core.I18nUtil;
/**
* Implementation of {@link FeedbackService} interface.
* It is responsible for sendint a feedback email with content a DSpace user
* fills from feedback section of DSpace.
*/
public class FeedbackServiceImpl implements FeedbackService {
@Override
public void sendEmail(Context context, HttpServletRequest request, String recipientEmail, String senderEmail,
String message, String page) throws IOException, MessagingException {
String session = request.getHeader("x-correlation-id");
String agent = request.getHeader("User-Agent");
String currentUserEmail = StringUtils.EMPTY;
if (Objects.nonNull(context.getCurrentUser())) {
currentUserEmail = context.getCurrentUser().getEmail();
}
Email email = Email.getEmail(I18nUtil.getEmailFilename(context.getCurrentLocale(), "feedback"));
email.addRecipient(recipientEmail);
email.addArgument(new Date()); // Date
email.addArgument(senderEmail); // Email
email.addArgument(currentUserEmail); // Logged in as
email.addArgument(page); // Referring page
email.addArgument(agent); // User agent
email.addArgument(session); // Session ID
email.addArgument(message); // The feedback itself
email.send();
}
}

View File

@@ -392,7 +392,7 @@ public class ItemDAOImpl extends AbstractHibernateDSODAO<Item> implements ItemDA
public Iterator<Item> findByLastModifiedSince(Context context, Date since)
throws SQLException {
Query query = createQuery(context,
"SELECT i FROM item i WHERE last_modified > :last_modified ORDER BY id");
"SELECT i FROM Item i WHERE last_modified > :last_modified ORDER BY id");
query.setParameter("last_modified", since, TemporalType.TIMESTAMP);
return iterate(query);
}

View File

@@ -0,0 +1,36 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.content.service;
import java.io.IOException;
import javax.mail.MessagingException;
import javax.servlet.http.HttpServletRequest;
import org.dspace.core.Context;
/**
* Service interface class for the Feedback object.
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
public interface FeedbackService {
/**
* This method sends the feeback email to the recipient passed as parameter
* @param context current DSpace application context
* @param request current servlet request
* @param recipientEmail recipient to which mail is sent
* @param senderEmail email address of the sender
* @param message message body
* @param page page from which user accessed and filled feedback form
* @throws IOException
* @throws MessagingException
*/
public void sendEmail(Context context, HttpServletRequest request, String recipientEmail, String senderEmail,
String message, String page) throws IOException, MessagingException;
}

View File

@@ -15,7 +15,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Bitstream;
@@ -154,10 +153,10 @@ public class CitationPage extends AbstractCurationTask {
.append(" is citable.");
try {
//Create the cited document
Pair<InputStream, Long> citedDocument =
citationDocument.makeCitedDocument(Curator.curationContext(), bitstream);
InputStream citedInputStream =
citationDocument.makeCitedDocument(Curator.curationContext(), bitstream).getLeft();
//Add the cited document to the approiate bundle
this.addCitedPageToItem(citedDocument.getLeft(), bundle, pBundle,
this.addCitedPageToItem(citedInputStream, bundle, pBundle,
dBundle, displayMap, item, bitstream);
} catch (Exception e) {
//Could be many things, but nothing that should be

View File

@@ -303,7 +303,12 @@ public class CitationDocumentServiceImpl implements CitationDocumentService, Ini
PDDocument sourceDocument = new PDDocument();
try {
Item item = (Item) bitstreamService.getParentObject(context, bitstream);
sourceDocument = sourceDocument.load(bitstreamService.retrieve(context, bitstream));
final InputStream inputStream = bitstreamService.retrieve(context, bitstream);
try {
sourceDocument = sourceDocument.load(inputStream);
} finally {
inputStream.close();
}
PDPage coverPage = new PDPage(citationPageFormat);
generateCoverPage(context, document, coverPage, item);
addCoverPageToDocument(document, sourceDocument, coverPage);
@@ -313,7 +318,7 @@ public class CitationDocumentServiceImpl implements CitationDocumentService, Ini
document.save(out);
byte[] data = out.toByteArray();
return Pair.of((InputStream) new ByteArrayInputStream(data), Long.valueOf(data.length));
return Pair.of(new ByteArrayInputStream(data), Long.valueOf(data.length));
}
} finally {

View File

@@ -7,10 +7,10 @@
*/
package org.dspace.iiif.canvasdimension;
import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_HEIGHT;
import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_IMAGE;
import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_HEIGHT_QUALIFIER;
import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_IMAGE_ELEMENT;
import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_SCHEMA;
import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_WIDTH;
import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_WIDTH_QUALIFIER;
import java.io.IOException;
import java.io.InputStream;
@@ -62,7 +62,8 @@ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionServic
private int processed = 0;
// used to check for existing canvas dimension
private static final String IIIF_WIDTH_METADATA = "iiif.image.width";
private static final String IIIF_WIDTH_METADATA = METADATA_IIIF_SCHEMA + "." + METADATA_IIIF_IMAGE_ELEMENT +
"." + METADATA_IIIF_WIDTH_QUALIFIER;
@Override
public void setForceProcessing(boolean force) {
@@ -209,13 +210,13 @@ public class IIIFCanvasDimensionServiceImpl implements IIIFCanvasDimensionServic
*/
private boolean setBitstreamMetadata(Context context, Bitstream bitstream, int[] dims) throws SQLException {
dSpaceObjectService.clearMetadata(context, bitstream, METADATA_IIIF_SCHEMA,
METADATA_IIIF_IMAGE, METADATA_IIIF_WIDTH, Item.ANY);
METADATA_IIIF_IMAGE_ELEMENT, METADATA_IIIF_WIDTH_QUALIFIER, Item.ANY);
dSpaceObjectService.setMetadataSingleValue(context, bitstream, METADATA_IIIF_SCHEMA,
METADATA_IIIF_IMAGE, METADATA_IIIF_WIDTH, null, String.valueOf(dims[0]));
METADATA_IIIF_IMAGE_ELEMENT, METADATA_IIIF_WIDTH_QUALIFIER, null, String.valueOf(dims[0]));
dSpaceObjectService.clearMetadata(context, bitstream, METADATA_IIIF_SCHEMA,
METADATA_IIIF_IMAGE, METADATA_IIIF_HEIGHT, Item.ANY);
METADATA_IIIF_IMAGE_ELEMENT, METADATA_IIIF_HEIGHT_QUALIFIER, Item.ANY);
dSpaceObjectService.setMetadataSingleValue(context, bitstream, METADATA_IIIF_SCHEMA,
METADATA_IIIF_IMAGE, METADATA_IIIF_HEIGHT, null, String.valueOf(dims[1]));
METADATA_IIIF_IMAGE_ELEMENT, METADATA_IIIF_HEIGHT_QUALIFIER, null, String.valueOf(dims[1]));
if (!isQuiet) {
System.out.println("Added IIIF canvas metadata to bitstream: " + bitstream.getID());
}

View File

@@ -36,9 +36,11 @@ public class IIIFSharedUtils {
protected static final String IMAGE_SERVER_PATH = "iiif.image.server";
// IIIF metadata definitions
public static final String METADATA_IIIF_SCHEMA = "iiif";
public static final String METADATA_IIIF_IMAGE = "image";
public static final String METADATA_IIIF_HEIGHT = "height";
public static final String METADATA_IIIF_WIDTH = "width";
public static final String METADATA_IIIF_IMAGE_ELEMENT = "image";
public static final String METADATA_IIIF_TOC_ELEMENT = "toc";
public static final String METADATA_IIIF_LABEL_ELEMENT = "label";
public static final String METADATA_IIIF_HEIGHT_QUALIFIER = "height";
public static final String METADATA_IIIF_WIDTH_QUALIFIER = "width";
protected static final ConfigurationService configurationService
= DSpaceServicesFactory.getInstance().getConfigurationService();

View File

@@ -7,10 +7,10 @@
*/
package org.dspace.app.iiif.service.utils;
import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_HEIGHT;
import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_IMAGE;
import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_HEIGHT_QUALIFIER;
import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_IMAGE_ELEMENT;
import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_SCHEMA;
import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_WIDTH;
import static org.dspace.iiif.util.IIIFSharedUtils.METADATA_IIIF_WIDTH_QUALIFIER;
import java.sql.SQLException;
import java.util.ArrayList;
@@ -66,11 +66,11 @@ public class IIIFUtils {
// metadata used to set the iiif viewing hint
public static final String METADATA_IIIF_VIEWING_HINT = "iiif.viewing.hint";
// metadata used to set the width of the canvas that has not an explicit name
public static final String METADATA_IMAGE_WIDTH = METADATA_IIIF_SCHEMA + "." + METADATA_IIIF_IMAGE
+ "." + METADATA_IIIF_WIDTH;
public static final String METADATA_IMAGE_WIDTH = METADATA_IIIF_SCHEMA + "." + METADATA_IIIF_IMAGE_ELEMENT
+ "." + METADATA_IIIF_WIDTH_QUALIFIER;
// metadata used to set the height of the canvas that has not an explicit name
public static final String METADATA_IMAGE_HEIGHT = METADATA_IIIF_SCHEMA + "." + METADATA_IIIF_IMAGE
+ "." + METADATA_IIIF_HEIGHT;
public static final String METADATA_IMAGE_HEIGHT = METADATA_IIIF_SCHEMA + "." + METADATA_IIIF_IMAGE_ELEMENT
+ "." + METADATA_IIIF_HEIGHT_QUALIFIER;
// string used in the metadata toc as separator among the different levels
public static final String TOC_SEPARATOR = "|||";

View File

@@ -38,6 +38,7 @@ import org.dspace.content.service.BitstreamFormatService;
import org.dspace.content.service.BitstreamService;
import org.dspace.core.Context;
import org.dspace.disseminate.service.CitationDocumentService;
import org.dspace.eperson.EPerson;
import org.dspace.services.ConfigurationService;
import org.dspace.services.EventService;
import org.dspace.usage.UsageEvent;
@@ -108,6 +109,8 @@ public class BitstreamRestController {
Context context = ContextUtil.obtainContext(request);
Bitstream bit = bitstreamService.find(context, uuid);
EPerson currentUser = context.getCurrentUser();
if (bit == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return null;
@@ -118,8 +121,6 @@ public class BitstreamRestController {
String mimetype = format.getMIMEType();
String name = getBitstreamName(bit, format);
Pair<InputStream, Long> bitstreamTuple = getBitstreamInputStreamAndSize(context, bit);
if (StringUtils.isBlank(request.getHeader("Range"))) {
//We only log a download request when serving a request without Range header. This is because
//a browser always sends a regular request first to check for Range support.
@@ -131,14 +132,20 @@ public class BitstreamRestController {
bit));
}
// Pipe the bits
InputStream is = bitstreamTuple.getLeft();
try {
HttpHeadersInitializer httpHeadersInitializer = HttpHeadersInitializer
.fromInputStream(is)
long filesize;
if (citationDocumentService.isCitationEnabledForBitstream(bit, context)) {
final Pair<InputStream, Long> citedDocument = citationDocumentService.makeCitedDocument(context, bit);
filesize = citedDocument.getRight();
citedDocument.getLeft().close();
} else {
filesize = bit.getSizeBytes();
}
HttpHeadersInitializer httpHeadersInitializer = new HttpHeadersInitializer()
.withBufferSize(BUFFER_SIZE)
.withFileName(name)
.withLength(bitstreamTuple.getRight())
.withLength(filesize)
.withChecksum(bit.getChecksum())
.withMimetype(mimetype)
.with(request)
@@ -150,13 +157,15 @@ public class BitstreamRestController {
//Determine if we need to send the file as a download or if the browser can open it inline
long dispositionThreshold = configurationService.getLongProperty("webui.content_disposition_threshold");
if (dispositionThreshold >= 0 && bitstreamTuple.getRight() > dispositionThreshold) {
if (dispositionThreshold >= 0 && filesize > dispositionThreshold) {
httpHeadersInitializer.withDisposition(HttpHeadersInitializer.CONTENT_DISPOSITION_ATTACHMENT);
}
org.dspace.app.rest.utils.BitstreamResource bitstreamResource =
new org.dspace.app.rest.utils.BitstreamResource(is, name, uuid, bit.getSizeBytes());
new org.dspace.app.rest.utils.BitstreamResource(
bit, name, uuid, filesize, currentUser != null ? currentUser.getID() : null);
//We have all the data we need, close the connection to the database so that it doesn't stay open during
//download/streaming
@@ -171,34 +180,12 @@ public class BitstreamRestController {
} catch (ClientAbortException ex) {
log.debug("Client aborted the request before the download was completed. " +
"Client is probably switching to a Range request.", ex);
} catch (Exception e) {
throw e;
}
return null;
}
private Pair<InputStream, Long> getBitstreamInputStreamAndSize(Context context, Bitstream bit)
throws SQLException, IOException, AuthorizeException {
if (citationDocumentService.isCitationEnabledForBitstream(bit, context)) {
return generateBitstreamWithCitation(context, bit);
} else {
return Pair.of(bitstreamService.retrieve(context, bit),bit.getSizeBytes());
}
}
private Pair<InputStream, Long> generateBitstreamWithCitation(Context context, Bitstream bitstream)
throws SQLException, IOException, AuthorizeException {
//Create the cited document
Pair<InputStream, Long> citationDocument = citationDocumentService.makeCitedDocument(context, bitstream);
if (citationDocument.getLeft() == null) {
log.error("CitedDocument was null");
} else {
if (log.isDebugEnabled()) {
log.debug("CitedDocument was ok, has size " + citationDocument.getRight());
}
}
return citationDocument;
}
private String getBitstreamName(Bitstream bit, BitstreamFormat format) {
String name = bit.getName();
if (name == null) {

View File

@@ -117,8 +117,7 @@ public class SitemapRestController {
HttpServletRequest request) throws SQLException, IOException {
// Pipe the bits
try (InputStream is = new FileInputStream(foundSitemapFile)) {
HttpHeadersInitializer sender = HttpHeadersInitializer
.fromInputStream(is)
HttpHeadersInitializer sender = new HttpHeadersInitializer()
.withBufferSize(BUFFER_SIZE)
.withFileName(foundSitemapFile.getName())
.withLength(foundSitemapFile.length())

View File

@@ -0,0 +1,49 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.authorization.impl;
import java.sql.SQLException;
import org.apache.commons.lang.StringUtils;
import org.dspace.app.rest.authorization.AuthorizationFeature;
import org.dspace.app.rest.authorization.AuthorizationFeatureDocumentation;
import org.dspace.app.rest.model.BaseObjectRest;
import org.dspace.app.rest.model.SiteRest;
import org.dspace.core.Context;
import org.dspace.services.ConfigurationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* The send feedback feature. It can be used to verify if the parameter that contain
* recipient e-mail is configured.
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com)
*/
@Component
@AuthorizationFeatureDocumentation(name = CanSendFeedbackFeature.NAME,
description = "It can be used to verify if the parameter that contain recipient e-mail is configured.")
public class CanSendFeedbackFeature implements AuthorizationFeature {
public static final String NAME = "canSendFeedback";
@Autowired
private ConfigurationService configurationService;
@Override
@SuppressWarnings("rawtypes")
public boolean isAuthorized(Context context, BaseObjectRest object) throws SQLException {
String recipientEmail = configurationService.getProperty("feedback.recipient");
return StringUtils.isNotBlank(recipientEmail);
}
@Override
public String[] getSupportedTypes() {
return new String[] { SiteRest.CATEGORY + "." + SiteRest.NAME };
}
}

View File

@@ -0,0 +1,30 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* When a request is malformed, we use this exception to indicate this to the client
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com)
*/
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Not Found")
public class DSpaceFeedbackNotFoundException extends RuntimeException {
private static final long serialVersionUID = 4631940402294095433L;
public DSpaceFeedbackNotFoundException(String message) {
this(message, null);
}
public DSpaceFeedbackNotFoundException(String message, Exception e) {
super(message, e);
}
}

View File

@@ -0,0 +1,75 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.dspace.app.rest.RestResourceController;
/**
* The REST object for the Feedback objects
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com)
*/
public class FeedbackRest extends BaseObjectRest<Integer> {
private static final long serialVersionUID = 1L;
public static final String NAME = "feedback";
public static final String CATEGORY = RestAddressableModel.TOOLS;
private String page;
private String email;
private String message;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getPage() {
return page;
}
public void setPage(String page) {
this.page = page;
}
@Override
@JsonIgnore
public Integer getId() {
return id;
}
@Override
@JsonIgnore
public String getType() {
return NAME;
}
@Override
public String getCategory() {
return CATEGORY;
}
@Override
@SuppressWarnings("rawtypes")
public Class getController() {
return RestResourceController.class;
}
}

View File

@@ -0,0 +1,103 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.repository;
import java.io.IOException;
import java.sql.SQLException;
import javax.mail.MessagingException;
import javax.servlet.http.HttpServletRequest;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang.StringUtils;
import org.dspace.app.rest.exception.DSpaceBadRequestException;
import org.dspace.app.rest.exception.DSpaceFeedbackNotFoundException;
import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException;
import org.dspace.app.rest.exception.UnprocessableEntityException;
import org.dspace.app.rest.model.FeedbackRest;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.service.FeedbackService;
import org.dspace.core.Context;
import org.dspace.services.ConfigurationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
/**
* This is the Repository that takes care of the operations on the {@link FeedbackRest} objects
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
@Component(FeedbackRest.CATEGORY + "." + FeedbackRest.NAME)
public class FeedbackRestRepository extends DSpaceRestRepository<FeedbackRest, Integer> {
@Autowired
private FeedbackService feedbackService;
@Autowired
private ConfigurationService configurationService;
@Override
@PreAuthorize("hasAuthority('AUTHENTICATED')")
public Page<FeedbackRest> findAll(Context context, Pageable pageable) {
throw new RepositoryMethodNotImplementedException(FeedbackRest.NAME, "findAll");
}
@Override
@PreAuthorize("hasAuthority('AUTHENTICATED')")
public FeedbackRest findOne(Context context, Integer id) {
throw new RepositoryMethodNotImplementedException(FeedbackRest.NAME, "findOne");
}
@Override
@PreAuthorize("permitAll()")
protected FeedbackRest createAndReturn(Context context) throws AuthorizeException, SQLException {
HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest();
ObjectMapper mapper = new ObjectMapper();
FeedbackRest feedbackRest = null;
String recipientEmail = configurationService.getProperty("feedback.recipient");
if (StringUtils.isBlank(recipientEmail)) {
throw new DSpaceFeedbackNotFoundException("Feedback cannot be sent at this time, Feedback recipient " +
"is disabled");
}
try {
feedbackRest = mapper.readValue(req.getInputStream(), FeedbackRest.class);
} catch (IOException exIO) {
throw new UnprocessableEntityException("error parsing the body " + exIO.getMessage(), exIO);
}
String senderEmail = feedbackRest.getEmail();
String message = feedbackRest.getMessage();
if (StringUtils.isBlank(senderEmail) || StringUtils.isBlank(message)) {
throw new DSpaceBadRequestException("e-mail and message fields are mandatory!");
}
try {
feedbackService.sendEmail(context, req, recipientEmail, senderEmail, message, feedbackRest.getPage());
} catch (IOException | MessagingException e) {
throw new RuntimeException(e.getMessage(), e);
}
return null;
}
@Override
public Class<FeedbackRest> getDomainClass() {
return FeedbackRest.class;
}
public FeedbackService getFeedbackService() {
return feedbackService;
}
public void setFeedbackService(FeedbackService feedbackService) {
this.feedbackService = feedbackService;
}
}

View File

@@ -9,8 +9,19 @@ package org.dspace.app.rest.utils;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.UUID;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Bitstream;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.BitstreamService;
import org.dspace.core.Context;
import org.dspace.disseminate.service.CitationDocumentService;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.eperson.service.EPersonService;
import org.dspace.utils.DSpace;
import org.springframework.core.io.AbstractResource;
/**
@@ -21,16 +32,24 @@ import org.springframework.core.io.AbstractResource;
*/
public class BitstreamResource extends AbstractResource {
private InputStream inputStream;
private Bitstream bitstream;
private String name;
private UUID uuid;
private long sizeBytes;
private UUID currentUserUUID;
public BitstreamResource(InputStream inputStream, String name, UUID uuid, long sizeBytes) {
this.inputStream = inputStream;
private BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService();
private EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
private CitationDocumentService citationDocumentService =
new DSpace().getServiceManager()
.getServicesByType(CitationDocumentService.class).get(0);
public BitstreamResource(Bitstream bitstream, String name, UUID uuid, long sizeBytes, UUID currentUserUUID) {
this.bitstream = bitstream;
this.name = name;
this.uuid = uuid;
this.sizeBytes = sizeBytes;
this.currentUserUUID = currentUserUUID;
}
@Override
@@ -40,7 +59,28 @@ public class BitstreamResource extends AbstractResource {
@Override
public InputStream getInputStream() throws IOException {
return inputStream;
Context context = new Context();
try {
EPerson currentUser = ePersonService.find(context, currentUserUUID);
context.setCurrentUser(currentUser);
InputStream out;
if (citationDocumentService.isCitationEnabledForBitstream(bitstream, context)) {
out = citationDocumentService.makeCitedDocument(context, bitstream).getLeft();
} else {
out = bitstreamService.retrieve(context, bitstream);
}
return out;
} catch (SQLException | AuthorizeException e) {
throw new IOException(e);
} finally {
try {
context.complete();
} catch (SQLException e) {
throw new IOException(e);
}
}
}
@Override

View File

@@ -10,9 +10,7 @@ package org.dspace.app.rest.utils;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import javax.servlet.http.HttpServletRequest;
@@ -64,7 +62,6 @@ public class HttpHeadersInitializer {
//no-cache so request is always performed for logging
private static final String CACHE_CONTROL_SETTING = "private,no-cache";
private BufferedInputStream inputStream;
private HttpServletRequest request;
private HttpServletResponse response;
private String contentType;
@@ -74,14 +71,8 @@ public class HttpHeadersInitializer {
private String fileName;
private String checksum;
public HttpHeadersInitializer(final InputStream inputStream) {
public HttpHeadersInitializer() {
//Convert to BufferedInputStream so we can re-read the stream
this.inputStream = new BufferedInputStream(inputStream);
}
public static HttpHeadersInitializer fromInputStream(InputStream inputStream) {
return new HttpHeadersInitializer(inputStream);
}
public HttpHeadersInitializer with(HttpServletRequest httpRequest) {
@@ -198,12 +189,6 @@ public class HttpHeadersInitializer {
return false;
}
if (inputStream == null) {
log.error("Input stream has no content");
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return false;
}
if (StringUtils.isEmpty(fileName)) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return false;

View File

@@ -0,0 +1,133 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNotNull;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.dspace.app.rest.model.FeedbackRest;
import org.dspace.app.rest.repository.FeedbackRestRepository;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.content.FeedbackServiceImpl;
import org.dspace.content.service.FeedbackService;
import org.dspace.services.ConfigurationService;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Integration test class for the feedback endpoint
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk@4science.com)
*/
public class FeedbackRestRepositoryIT extends AbstractControllerIntegrationTest {
@Autowired
private ConfigurationService configurationService;
@Autowired
private FeedbackRestRepository feedbackRestRepository;
@Test
public void findAllTest() throws Exception {
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/tools/feedbacks"))
.andExpect(status().isMethodNotAllowed());
}
@Test
public void findOneTest() throws Exception {
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/tools/feedbacks/1"))
.andExpect(status().isMethodNotAllowed());
}
@Test
public void sendFeedbackTest() throws Exception {
configurationService.setProperty("feedback.recipient", "recipient.email@test.com");
FeedbackService originFeedbackService = feedbackRestRepository.getFeedbackService();
try {
FeedbackService feedbackServiceMock = mock (FeedbackServiceImpl.class);
feedbackRestRepository.setFeedbackService(feedbackServiceMock);
ObjectMapper mapper = new ObjectMapper();
FeedbackRest feedbackRest = new FeedbackRest();
feedbackRest.setEmail("misha.boychuk@test.com");
feedbackRest.setMessage("My feedback!");
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(post("/api/tools/feedbacks")
.content(mapper.writeValueAsBytes(feedbackRest))
.contentType(contentType))
.andExpect(status().isCreated());
verify(feedbackServiceMock).sendEmail(isNotNull(), isNotNull(), eq("recipient.email@test.com"),
eq("misha.boychuk@test.com"), eq("My feedback!"), isNull());
verifyNoMoreInteractions(feedbackServiceMock);
} finally {
feedbackRestRepository.setFeedbackService(originFeedbackService);
}
}
@Test
public void sendFeedbackWithRecipientEmailNotConfiguredTest() throws Exception {
configurationService.setProperty("feedback.recipient", null);
FeedbackService originFeedbackService = feedbackRestRepository.getFeedbackService();
try {
FeedbackService feedbackServiceMock = mock (FeedbackServiceImpl.class);
feedbackRestRepository.setFeedbackService(feedbackServiceMock);
ObjectMapper mapper = new ObjectMapper();
FeedbackRest feedbackRest = new FeedbackRest();
feedbackRest.setEmail("misha.boychuk@test.com");
feedbackRest.setMessage("My feedback!");
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(post("/api/tools/feedbacks")
.content(mapper.writeValueAsBytes(feedbackRest))
.contentType(contentType))
.andExpect(status().isNotFound());
verifyNoMoreInteractions(feedbackServiceMock);
} finally {
feedbackRestRepository.setFeedbackService(originFeedbackService);
}
}
@Test
public void sendFeedbackBadRequestTest() throws Exception {
FeedbackService originFeedbackService = feedbackRestRepository.getFeedbackService();
try {
FeedbackService feedbackServiceMock = mock (FeedbackServiceImpl.class);
feedbackRestRepository.setFeedbackService(feedbackServiceMock);
ObjectMapper mapper = new ObjectMapper();
FeedbackRest feedbackRest = new FeedbackRest();
feedbackRest.setMessage("My feedback!");
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(post("/api/tools/feedbacks")
.content(mapper.writeValueAsBytes(feedbackRest))
.contentType(contentType))
.andExpect(status().isBadRequest());
verifyNoMoreInteractions(feedbackServiceMock);
} finally {
feedbackRestRepository.setFeedbackService(originFeedbackService);
}
}
}

View File

@@ -0,0 +1,110 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.authorization;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.dspace.app.rest.authorization.impl.CanSendFeedbackFeature;
import org.dspace.app.rest.converter.SiteConverter;
import org.dspace.app.rest.matcher.AuthorizationMatcher;
import org.dspace.app.rest.model.SiteRest;
import org.dspace.app.rest.projection.DefaultProjection;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.content.Site;
import org.dspace.content.service.SiteService;
import org.dspace.services.ConfigurationService;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Test for the canSendFeedback authorization feature.
*
* @author Mykhaylo Boychuk (mykhaylo.boychuk at 4science.com)
*/
public class CanSendFeedbackFeatureIT extends AbstractControllerIntegrationTest {
@Autowired
private SiteService siteService;
@Autowired
private SiteConverter siteConverter;
@Autowired
private ConfigurationService configurationService;
@Autowired
private AuthorizationFeatureService authorizationFeatureService;
final String feature = "canSendFeedback";
private AuthorizationFeature canSendFeedbackFeature;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
canSendFeedbackFeature = authorizationFeatureService.find(CanSendFeedbackFeature.NAME);
}
@Test
public void canSendFeedbackFeatureTest() throws Exception {
configurationService.setProperty("feedback.recipient", "myemail@test.com");
Site site = siteService.findSite(context);
SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT);
String tokenAdmin = getAuthToken(admin.getEmail(), password);
String tokenEperson = getAuthToken(eperson.getEmail(), password);
// define authorizations
Authorization authAdminSite = new Authorization(admin, canSendFeedbackFeature, siteRest);
Authorization authAnonymousSite = new Authorization(null, canSendFeedbackFeature, siteRest);
Authorization authEPersonSite = new Authorization(eperson, canSendFeedbackFeature, siteRest);
getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + authAdminSite.getID()))
.andExpect(status().isOk())
.andExpect(jsonPath("$", Matchers.is(
AuthorizationMatcher.matchAuthorization(authAdminSite))));
getClient(tokenEperson).perform(get("/api/authz/authorizations/" + authEPersonSite.getID()))
.andExpect(status().isOk())
.andExpect(jsonPath("$", Matchers.is(
AuthorizationMatcher.matchAuthorization(authEPersonSite))));
getClient().perform(get("/api/authz/authorizations/" + authAnonymousSite.getID()))
.andExpect(status().isOk())
.andExpect(jsonPath("$", Matchers.is(
AuthorizationMatcher.matchAuthorization(authAnonymousSite))));
}
@Test
public void canSendFeedbackFeatureWithRecipientEmailNotConfiguredTest() throws Exception {
configurationService.setProperty("feedback.recipient", null);
Site site = siteService.findSite(context);
SiteRest siteRest = siteConverter.convert(site, DefaultProjection.DEFAULT);
String tokenAdmin = getAuthToken(admin.getEmail(), password);
String tokenEperson = getAuthToken(eperson.getEmail(), password);
// define authorizations
Authorization authAdminSite = new Authorization(admin, canSendFeedbackFeature, siteRest);
Authorization authAnonymousSite = new Authorization(null, canSendFeedbackFeature, siteRest);
Authorization authEPersonSite = new Authorization(eperson, canSendFeedbackFeature, siteRest);
getClient(tokenAdmin).perform(get("/api/authz/authorizations/" + authAdminSite.getID()))
.andExpect(status().isNotFound());
getClient(tokenEperson).perform(get("/api/authz/authorizations/" + authEPersonSite.getID()))
.andExpect(status().isNotFound());
getClient().perform(get("/api/authz/authorizations/" + authAnonymousSite.getID()))
.andExpect(status().isNotFound());
}
}

View File

@@ -157,7 +157,7 @@ mail.from.address = dspace-noreply@myu.edu
# When feedback is submitted via the Feedback form, it is sent to this address
# Currently limited to one recipient!
# TODO: UNSUPPORTED in DSpace 7.0
# if this property is empty or commented out, feedback form is disabled
feedback.recipient = dspace-help@myu.edu
# General site administration (Webmaster) e-mail

View File

@@ -69,7 +69,8 @@ iiif.license.uri = dc.rights.uri
# static text to be used as attribution in the iiif manifests
iiif.attribution = ${dspace.name}
# URL for logo. If defined, the logo will be added to the manifest.
# URL for logo. A small image that represents an individual or organization associated with the
# resource. It is added to the IIIF manifest.
iiif.logo.image = ${dspace.ui.url}/assets/images/dspace-logo.svg
# (optional) one of individuals, paged or continuous. Can be overridden at the item level via

View File

@@ -55,6 +55,7 @@
<bean class="org.dspace.content.EntityServiceImpl"/>
<bean class="org.dspace.content.RelationshipTypeServiceImpl"/>
<bean class="org.dspace.content.RelationshipMetadataServiceImpl"/>
<bean class="org.dspace.content.FeedbackServiceImpl"/>
<bean class="org.dspace.scripts.ProcessServiceImpl"/>
<bean class="org.dspace.scripts.ScriptServiceImpl"/>

View File

@@ -26,6 +26,7 @@
<!-- HelpDesk to instead get RequestItem emails-->
<!--<bean class="org.dspace.app.requestitem.RequestItemHelpdeskStrategy"
id="org.dspace.app.requestitem.RequestItemAuthorExtractor"></bean>-->
id="org.dspace.app.requestitem.RequestItemAuthorExtractor"
autowire-candidate='true'></bean>-->
</beans>