mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-07 01:54:22 +00:00
Added iiif service and utility classes.
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* 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.iiif.service;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.dspace.app.rest.iiif.model.generator.CanvasGenerator;
|
||||
import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator;
|
||||
import org.dspace.app.rest.iiif.model.generator.ImageServiceGenerator;
|
||||
import org.dspace.app.rest.iiif.model.generator.ProfileGenerator;
|
||||
import org.dspace.app.rest.iiif.service.util.IIIFUtils;
|
||||
import org.dspace.app.rest.iiif.service.util.ImageProfileUtil;
|
||||
import org.dspace.app.rest.iiif.service.util.ThumbProfileUtil;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* Base class for IIIF responses.
|
||||
*/
|
||||
public abstract class AbstractResourceService {
|
||||
|
||||
/**
|
||||
* These values are defined in dspace configuration.
|
||||
*/
|
||||
protected String IIIF_ENDPOINT;
|
||||
protected String IMAGE_SERVICE;
|
||||
protected String SEARCH_URL;
|
||||
protected String CLIENT_URL;
|
||||
protected String BITSTREAM_PATH_PREFIX;
|
||||
/**
|
||||
* Possible values: "paged" or "individuals". Typically paged is preferred
|
||||
* for documents. However, it can be overridden in configuration if necessary
|
||||
* for the viewer client.
|
||||
*/
|
||||
protected static String DOCUMENT_VIEWING_HINT;
|
||||
|
||||
// TODO: should these bundle settings be added to dspace configuration or hard-coded here?
|
||||
// The DSpace bundle used for IIIF entity types.
|
||||
protected static final String IIIF_BUNDLE = "IIIF";
|
||||
// The DSpace bundle for other content related to item.
|
||||
protected static final String OTHER_CONTENT_BUNDLE = "OtherContent";
|
||||
|
||||
// Paths for IIIF Image API requests.
|
||||
protected static final String THUMBNAIL_PATH = "/full/,90/0/default.jpg";
|
||||
protected static final String IMAGE_PATH = "/full/full/0/default.jpg";
|
||||
|
||||
@Autowired
|
||||
IIIFUtils utils;
|
||||
|
||||
@Autowired
|
||||
ThumbProfileUtil thumbUtil;
|
||||
|
||||
@Autowired
|
||||
ImageProfileUtil imageUtil;
|
||||
|
||||
@Autowired
|
||||
ImageContentGenerator imageContent;
|
||||
|
||||
@Autowired
|
||||
ImageServiceGenerator imageService;
|
||||
|
||||
/**
|
||||
* Set constants using DSpace configuration definitions.
|
||||
* @param configurationService the DSpace configuration service
|
||||
*/
|
||||
protected void setConfiguration(ConfigurationService configurationService) {
|
||||
IIIF_ENDPOINT = configurationService.getProperty("iiif.url");
|
||||
IMAGE_SERVICE = configurationService.getProperty("iiif.image.server");
|
||||
SEARCH_URL = configurationService.getProperty("iiif.solr.search.url");
|
||||
BITSTREAM_PATH_PREFIX = configurationService.getProperty("iiif.bitstream.url");
|
||||
DOCUMENT_VIEWING_HINT = configurationService.getProperty("iiif.document.viewing.hint");
|
||||
CLIENT_URL = configurationService.getProperty("dspace.ui.url");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the manifest id from the provided uuid.
|
||||
* @param uuid the item id
|
||||
* @return the manifest identifier (url)
|
||||
*/
|
||||
protected String getManifestId(UUID uuid) {
|
||||
return IIIF_ENDPOINT + uuid + "/manifest";
|
||||
}
|
||||
|
||||
/**
|
||||
* Association of images with their respective canvases is done via annotations.
|
||||
* Only the annotations that associate images or parts of images are included in
|
||||
* the canvas in the images property. If a IIIF Image API service is available for
|
||||
* the image, then a link to the service’s base URI should be included.
|
||||
*
|
||||
* This method adds an image annotations to a canvas for both thumbnail and full size
|
||||
* images. The annotation references the IIIF image service.
|
||||
*
|
||||
* @param canvas the Canvas object.
|
||||
* @param mimeType the image mime type
|
||||
* @param bitstreamID the bitstream uuid
|
||||
*/
|
||||
protected void addImage(CanvasGenerator canvas, String mimeType, UUID bitstreamID) throws
|
||||
RuntimeException {
|
||||
canvas.addThumbnail(getThumbnailAnnotation(bitstreamID, mimeType));
|
||||
// Add image content resource to canvas facade.
|
||||
canvas.addImage(getImageContent(bitstreamID, mimeType, imageUtil.getImageProfile(), IMAGE_PATH).getResource());
|
||||
}
|
||||
|
||||
/**
|
||||
* A small image that depicts or pictorially represents the resource that
|
||||
* the property is attached to, such as the title page, a significant image
|
||||
* or rendering of a canvas with multiple content resources associated with it.
|
||||
* It is recommended that a IIIF Image API service be available for this image for
|
||||
* manipulations such as resizing.
|
||||
*
|
||||
* This method returns a thumbnail annotation that includes the IIIF image service.
|
||||
*
|
||||
* @param uuid the bitstream id
|
||||
* @return thumbnail Annotation
|
||||
*/
|
||||
protected ImageContentGenerator getThumbnailAnnotation(UUID uuid, String mimetype) throws
|
||||
RuntimeException {
|
||||
return getImageContent(uuid, mimetype, thumbUtil.getThumbnailProfile(), THUMBNAIL_PATH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Association of images with their respective canvases is done via annotations. The Open Annotation model
|
||||
* allows any resource to be associated with any other resource, or parts thereof, and it is reused for
|
||||
* both commentary and painting resources on the canvas.
|
||||
* @param uuid bitstream uuid
|
||||
* @param mimetype bitstream mimetype
|
||||
* @param profile the service profile
|
||||
* @param path the path component of the identifier
|
||||
* @return
|
||||
*/
|
||||
private ImageContentGenerator getImageContent(UUID uuid, String mimetype, ProfileGenerator profile, String path) {
|
||||
imageContent.setFormat(mimetype);
|
||||
imageContent.setIdentifier(IMAGE_SERVICE + uuid + path);
|
||||
imageContent.addService(getImageService(profile, uuid.toString()));
|
||||
return imageContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* A link to a service that makes more functionality available for the resource,
|
||||
* such as from an image to the base URI of an associated IIIF Image API service.
|
||||
*
|
||||
* @param profile service profile
|
||||
* @param uuid id of the image bitstream
|
||||
* @return object representing the Image Service
|
||||
*/
|
||||
private ImageServiceGenerator getImageService(ProfileGenerator profile, String uuid) {
|
||||
imageService.setIdentifier(IMAGE_SERVICE + uuid);
|
||||
imageService.setProfile(profile);
|
||||
return imageService;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* 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.iiif.service;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator;
|
||||
import org.dspace.app.rest.iiif.model.generator.AnnotationListGenerator;
|
||||
import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator;
|
||||
import org.dspace.app.rest.iiif.model.generator.PropertyValueGenerator;
|
||||
import org.dspace.app.rest.iiif.service.util.IIIFUtils;
|
||||
import org.dspace.content.Bitstream;
|
||||
import org.dspace.content.BitstreamFormat;
|
||||
import org.dspace.content.Bundle;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.service.BitstreamFormatService;
|
||||
import org.dspace.content.service.BitstreamService;
|
||||
import org.dspace.content.service.ItemService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class AnnotationListService extends AbstractResourceService {
|
||||
|
||||
@Autowired
|
||||
IIIFUtils utils;
|
||||
|
||||
@Autowired
|
||||
ItemService itemService;
|
||||
|
||||
@Autowired
|
||||
BitstreamService bitstreamService;
|
||||
|
||||
@Autowired
|
||||
BitstreamFormatService bitstreamFormatService;
|
||||
|
||||
@Autowired
|
||||
PropertyValueGenerator propertyValue;
|
||||
|
||||
@Autowired
|
||||
AnnotationListGenerator annotationList;
|
||||
|
||||
@Autowired
|
||||
AnnotationGenerator annotation;
|
||||
|
||||
@Autowired
|
||||
ExternalLinksGenerator otherContent;
|
||||
|
||||
public AnnotationListService(ConfigurationService configurationService) {
|
||||
setConfiguration(configurationService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an AnnotationList for bitstreams in the OtherContent bundle.
|
||||
* These resources are not appended directly to the manifest but can be accessed
|
||||
* via the seeAlso link.
|
||||
*
|
||||
* The semantics of this linking property may be extended to full text files, but
|
||||
* machine readable formats like ALTO, METS, and schema.org descriptions are preferred.
|
||||
*
|
||||
* @param context DSpace context
|
||||
* @param id bitstream uuid
|
||||
* @return AnnotationList as JSON
|
||||
*/
|
||||
public String getSeeAlsoAnnotations(Context context, UUID id)
|
||||
throws RuntimeException {
|
||||
/**
|
||||
* We need the DSpace item to proceed.
|
||||
*/
|
||||
Item item;
|
||||
try {
|
||||
item = itemService.find(context, id);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
}
|
||||
/**
|
||||
* AnnotationList requires an identifier.
|
||||
*/
|
||||
annotationList.setIdentifier(IIIF_ENDPOINT + id + "/manifest/seeAlso");
|
||||
/**
|
||||
* Get the "OtherContent" bundle for the item. Then add
|
||||
* Annotations for each bitstream found in the bundle.
|
||||
*/
|
||||
List<Bundle> bundles = utils.getBundle(item, OTHER_CONTENT_BUNDLE);
|
||||
if (bundles.size() > 0) {
|
||||
for (Bundle bundle : bundles) {
|
||||
List<Bitstream> bitstreams = bundle.getBitstreams();
|
||||
for (Bitstream bitstream : bitstreams) {
|
||||
BitstreamFormat format;
|
||||
String mimetype;
|
||||
try {
|
||||
format = bitstream.getFormat(context);
|
||||
mimetype = format.getMIMEType();
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
}
|
||||
annotation.setIdentifier(IIIF_ENDPOINT + bitstream.getID() + "/annot");
|
||||
annotation.setMotivation(AnnotationGenerator.LINKING);
|
||||
otherContent.setIdentifier(BITSTREAM_PATH_PREFIX
|
||||
+ "/"
|
||||
+ bitstream.getID()
|
||||
+ "/content");
|
||||
otherContent.setFormat(mimetype);
|
||||
otherContent.setLabel(bitstream.getName());
|
||||
annotation.setResource(otherContent);
|
||||
annotationList.addResource(annotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
return utils.asJson(annotationList.getResource());
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.rest.iiif.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.dspace.app.rest.iiif.model.generator.CanvasGenerator;
|
||||
import org.dspace.app.rest.iiif.model.info.Info;
|
||||
import org.dspace.app.rest.iiif.service.util.IIIFUtils;
|
||||
import org.dspace.content.Bitstream;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Canvases may be dereferenced separately from the manifest via their URIs.
|
||||
*/
|
||||
@Component
|
||||
public class CanvasLookupService extends AbstractResourceService {
|
||||
|
||||
@Autowired
|
||||
IIIFUtils utils;
|
||||
|
||||
@Autowired
|
||||
CanvasService canvasService;
|
||||
|
||||
public CanvasLookupService(ConfigurationService configurationService) {
|
||||
setConfiguration(configurationService);
|
||||
}
|
||||
|
||||
public String generateCanvas(Context context, Item item, String canvasId) {
|
||||
int canvasPosition = utils.getCanvasId(canvasId);
|
||||
Bitstream bitstream = utils.getBitstreamForCanvas(item, IIIF_BUNDLE, canvasPosition);
|
||||
if (bitstream == null) {
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
Info info =
|
||||
utils.validateInfoForSingleCanvas(utils.getInfo(context, item, IIIF_BUNDLE), canvasPosition);
|
||||
ArrayList<Bitstream> bitstreams = new ArrayList<>();
|
||||
bitstreams.add(bitstream);
|
||||
UUID bitstreamID = bitstream.getID();
|
||||
String mimeType = utils.getBitstreamMimeType(bitstream, context);
|
||||
CanvasGenerator canvas = canvasService.getCanvas(item.getID().toString(), info, canvasPosition);
|
||||
if (mimeType.contains("image/")) {
|
||||
addImage(canvas, mimeType, bitstreamID);
|
||||
}
|
||||
return utils.asJson(canvas.getResource());
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 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.iiif.service;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
import org.dspace.app.rest.iiif.model.generator.CanvasGenerator;
|
||||
import org.dspace.app.rest.iiif.model.info.Info;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
public class CanvasService extends AbstractResourceService {
|
||||
|
||||
private static final Logger log = Logger.getLogger(CanvasService.class);
|
||||
|
||||
// Default canvas dimensions.
|
||||
protected static final Integer DEFAULT_CANVAS_WIDTH = 1200;
|
||||
protected static final Integer DEFAULT_CANVAS_HEIGHT = 1600;
|
||||
|
||||
@Autowired
|
||||
CanvasGenerator canvas;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param configurationService the DSpace configuration service.
|
||||
*/
|
||||
public CanvasService(ConfigurationService configurationService) {
|
||||
setConfiguration(configurationService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a single Canvas object. If canvas parameters are provided by the
|
||||
* Info object they are used. If canvas parameters are unavailable, default values
|
||||
* are used instead.
|
||||
*
|
||||
* @param id manifest id
|
||||
* @param info parameters for this canvas
|
||||
* @param count the canvas position in the sequence.
|
||||
* @return canvas object
|
||||
*/
|
||||
protected CanvasGenerator getCanvas(String id, Info info, int count) {
|
||||
// Defaults settings.
|
||||
int canvasWidth = DEFAULT_CANVAS_WIDTH;
|
||||
int canvasHeight = DEFAULT_CANVAS_HEIGHT;
|
||||
int pagePosition = count + 1;
|
||||
String label = "Page " + pagePosition;
|
||||
// Override with settings from info.json, if available.
|
||||
if (info != null && info.getGlobalDefaults() != null && info.getCanvases() != null) {
|
||||
// Use global settings if activated.
|
||||
if (info.getGlobalDefaults().isActivated()) {
|
||||
// Create unique label by appending position to the default label.
|
||||
label = info.getGlobalDefaults().getLabel() + " " + pagePosition;
|
||||
canvasWidth = info.getGlobalDefaults().getWidth();
|
||||
canvasHeight = info.getGlobalDefaults().getHeight();
|
||||
} else if (info.getCanvases().get(count) != null) {
|
||||
if (info.getCanvases().get(count).getLabel().length() > 0) {
|
||||
// Individually defined canvas labels assumed unique, and are not incremented.
|
||||
label = info.getCanvases().get(count).getLabel();
|
||||
}
|
||||
canvasWidth = info.getCanvases().get(count).getWidth();
|
||||
canvasHeight = info.getCanvases().get(count).getHeight();
|
||||
}
|
||||
} else {
|
||||
log.info("Correctly formatted info.json was not found for item. Using application defaults.");
|
||||
}
|
||||
canvas.setIdentifier(IIIF_ENDPOINT + id + "/canvas/c" + count);
|
||||
canvas.setLabel(label);
|
||||
canvas.setHeight(canvasHeight);
|
||||
canvas.setWidth(canvasWidth);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ranges expect the Canvas object to have only an identifier. This method assures that the
|
||||
* injected canvas facade is empty before setting the identifier.
|
||||
* @param identifier the DSpace item identifier
|
||||
* @param startCanvas the position of the canvas in list
|
||||
* @return
|
||||
*/
|
||||
protected CanvasGenerator getRangeCanvasReference(String identifier, String startCanvas) {
|
||||
canvas.setIdentifier(IIIF_ENDPOINT + identifier + startCanvas);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,338 @@
|
||||
/**
|
||||
* 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.iiif.service;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.digitalcollections.iiif.model.sharedcanvas.AnnotationList;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.dspace.app.rest.iiif.model.generator.CanvasGenerator;
|
||||
import org.dspace.app.rest.iiif.model.generator.CanvasItemsGenerator;
|
||||
import org.dspace.app.rest.iiif.model.generator.ContentSearchGenerator;
|
||||
import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator;
|
||||
import org.dspace.app.rest.iiif.model.generator.ManifestGenerator;
|
||||
import org.dspace.app.rest.iiif.model.generator.RangeGenerator;
|
||||
import org.dspace.app.rest.iiif.model.info.Info;
|
||||
import org.dspace.app.rest.iiif.model.info.RangeModel;
|
||||
import org.dspace.app.rest.iiif.service.util.IIIFUtils;
|
||||
import org.dspace.content.Bitstream;
|
||||
import org.dspace.content.Bundle;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.MetadataValue;
|
||||
import org.dspace.content.service.ItemService;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Generates IIIF Manifest JSON response for a DSpace Item.
|
||||
*/
|
||||
@Component
|
||||
public class ManifestService extends AbstractResourceService {
|
||||
|
||||
private static final Logger log = Logger.getLogger(ManifestService.class);
|
||||
|
||||
private static final String PDF_DOWNLOAD_LABEL = "Download as PDF";
|
||||
private static final String RELATED_ITEM_LABEL = "DSpace item view";
|
||||
private static final String SEE_ALSO_LABEL = "More descriptions of this resource";
|
||||
|
||||
@Autowired
|
||||
protected ItemService itemService;
|
||||
|
||||
@Autowired
|
||||
CanvasService canvasService;
|
||||
|
||||
@Autowired
|
||||
ExternalLinksGenerator otherContentGenerator;
|
||||
|
||||
@Autowired
|
||||
ManifestGenerator manifestGenerator;
|
||||
|
||||
@Autowired
|
||||
CanvasItemsGenerator sequenceGenerator;
|
||||
|
||||
@Autowired
|
||||
RangeGenerator rangeGenerator;
|
||||
|
||||
@Autowired
|
||||
ContentSearchGenerator contentSearchGenerator;
|
||||
|
||||
@Autowired
|
||||
IIIFUtils utils;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param configurationService the DSpace configuration service.
|
||||
*/
|
||||
public ManifestService(ConfigurationService configurationService) {
|
||||
setConfiguration(configurationService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns serialized Manifest response for a DSpace item.
|
||||
*
|
||||
* @param item the DSpace Item
|
||||
* @param context the DSpace context
|
||||
* @return Manifest as JSON
|
||||
*/
|
||||
public String getManifest(Item item, Context context) {
|
||||
initializeManifestGenerator(item, context);
|
||||
return utils.asJson(manifestGenerator.getResource());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the Manifest for a DSpace item.
|
||||
*
|
||||
* @param item DSpace Item
|
||||
* @param context DSpace context
|
||||
* @return manifest object
|
||||
*/
|
||||
private void initializeManifestGenerator(Item item, Context context) {
|
||||
List<Bundle> bundles = utils.getIiifBundle(item, IIIF_BUNDLE);
|
||||
List<Bitstream> bitstreams = utils.getBitstreams(bundles);
|
||||
Info info = utils.validateInfoForManifest(utils.getInfo(context, item, IIIF_BUNDLE), bitstreams);
|
||||
manifestGenerator.setIdentifier(getManifestId(item.getID()));
|
||||
manifestGenerator.setLabel(item.getName());
|
||||
addRelated(item);
|
||||
addSearchService(item);
|
||||
addMetadata(item.getMetadata());
|
||||
addViewingHint(bitstreams.size());
|
||||
addThumbnail(bitstreams, context);
|
||||
addSequence(item, bitstreams, context, info);
|
||||
addRanges(info, item.getID().toString());
|
||||
addSeeAlso(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single sequence with canvases and item rendering (optional).
|
||||
* @param item DSpace Item
|
||||
* @param bitstreams list of bitstreams
|
||||
* @param context the DSpace context
|
||||
* @return a sequence of canvases
|
||||
*/
|
||||
private void addSequence(Item item, List<Bitstream> bitstreams, Context context, Info info) {
|
||||
sequenceGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/sequence/s0");
|
||||
if (bitstreams.size() > 0) {
|
||||
addCanvas(sequenceGenerator, context, item, bitstreams, info);
|
||||
}
|
||||
addRendering(sequenceGenerator, item, context);
|
||||
manifestGenerator.addSequence(sequenceGenerator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds DSpace Item metadata to the manifest.
|
||||
*
|
||||
* @param metadata list of DSpace metadata values
|
||||
*/
|
||||
private void addMetadata(List<MetadataValue> metadata) {
|
||||
for (MetadataValue meta : metadata) {
|
||||
String field = utils.getMetadataFieldName(meta);
|
||||
if (field.contentEquals("rights.uri")) {
|
||||
manifestGenerator.addMetadata(field, meta.getValue());
|
||||
manifestGenerator.addLicense(meta.getValue());
|
||||
} else if (field.contentEquals("description")) {
|
||||
// Add manifest description field.
|
||||
manifestGenerator.addDescription(field, meta.getValue());
|
||||
} else {
|
||||
// Exclude DSpace description.provenance field.
|
||||
if (!field.contentEquals("description.provenance")) {
|
||||
// Everything else, add to manifest metadata fields.
|
||||
manifestGenerator.addMetadata(field, meta.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A link to an external resource intended to be displayed directly to the user,
|
||||
* and is related to the resource that has the related property. Examples might
|
||||
* include a video or academic paper about the resource, a website, an HTML
|
||||
* description, and so forth.
|
||||
*
|
||||
* This method adds a link to the Item represented in the DSpace Angular UI.
|
||||
*
|
||||
* @param item the DSpace Item
|
||||
*/
|
||||
private void addRelated(Item item) {
|
||||
String url = CLIENT_URL + "/items/" + item.getID();
|
||||
otherContentGenerator.setIdentifier(url);
|
||||
otherContentGenerator.setFormat("text/html");
|
||||
otherContentGenerator.setLabel(RELATED_ITEM_LABEL);
|
||||
manifestGenerator.addRelated(otherContentGenerator);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method adds a canvas to the sequence for each item in the list of DSpace bitstreams.
|
||||
* To be added bitstreams must be on image mime type.
|
||||
*
|
||||
* @param sequence the sequence object
|
||||
* @param context the DSpace context
|
||||
* @param item the DSpace Item
|
||||
* @param bitstreams list of DSpace bitstreams
|
||||
*/
|
||||
private void addCanvas(CanvasItemsGenerator sequence, Context context, Item item,
|
||||
List<Bitstream> bitstreams, Info info) {
|
||||
/**
|
||||
* Counter tracks the position of the bitstream in the list and is used to create the canvas identifier.
|
||||
* Bitstream order is determined by position in the IIIF DSpace bundle.
|
||||
*/
|
||||
int counter = 0;
|
||||
for (Bitstream bitstream : bitstreams) {
|
||||
UUID bitstreamID = bitstream.getID();
|
||||
String mimeType = utils.getBitstreamMimeType(bitstream, context);
|
||||
if (utils.checkImageMimeType(mimeType)) {
|
||||
CanvasGenerator canvas = canvasService.getCanvas(item.getID().toString(), info, counter);
|
||||
addImage(canvas, mimeType, bitstreamID);
|
||||
if (counter == 2) {
|
||||
addImage(canvas, mimeType, bitstreamID);
|
||||
}
|
||||
sequence.addCanvas(canvas);
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A hint to the client as to the most appropriate method of displaying the resource.
|
||||
*
|
||||
* @param bitstreamCount count of bitstreams in the IIIF bundle.
|
||||
*/
|
||||
private void addViewingHint(int bitstreamCount) {
|
||||
if (bitstreamCount > 2) {
|
||||
manifestGenerator.addViewingHint(DOCUMENT_VIEWING_HINT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A link to a machine readable document that semantically describes the resource with
|
||||
* the seeAlso property, such as an XML or RDF description. This document could be used
|
||||
* for search and discovery or inferencing purposes, or just to provide a longer
|
||||
* description of the resource. May have one or more external descriptions related to it.
|
||||
*
|
||||
* This method appends an AnnotationList of resources found in the Item's OtherContent bundle.
|
||||
* A typical use case would be METS or ALTO files that describe the resource.
|
||||
*
|
||||
* @param item the DSpace Item.
|
||||
*/
|
||||
private void addSeeAlso(Item item) {
|
||||
List<Bundle> bundles = utils.getBundle(item, OTHER_CONTENT_BUNDLE);
|
||||
if (bundles.size() == 0) {
|
||||
return;
|
||||
}
|
||||
otherContentGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/manifest/seeAlso");
|
||||
otherContentGenerator.setType(AnnotationList.TYPE);
|
||||
otherContentGenerator.setLabel(SEE_ALSO_LABEL);
|
||||
manifestGenerator.addSeeAlso(otherContentGenerator);
|
||||
}
|
||||
|
||||
/**
|
||||
* A link to an external resource intended for display or download by a human user.
|
||||
* This property can be used to link from a manifest, collection or other resource
|
||||
* to the preferred viewing environment for that resource, such as a viewer page on
|
||||
* the publisher’s web site. Other uses include a rendering of a manifest as a PDF
|
||||
* or EPUB.
|
||||
*
|
||||
* This method looks for a PDF rendering in the Item's ORIGINAL bundle and adds
|
||||
* it to the Sequence if found.
|
||||
*
|
||||
* @param sequence Sequence object
|
||||
* @param item DSpace Item
|
||||
* @param context DSpace context
|
||||
*/
|
||||
private void addRendering(CanvasItemsGenerator sequence, Item item, Context context) {
|
||||
List<Bundle> bundles = item.getBundles("ORIGINAL");
|
||||
if (bundles.size() == 0) {
|
||||
return;
|
||||
}
|
||||
Bundle bundle = bundles.get(0);
|
||||
List<Bitstream> bitstreams = bundle.getBitstreams();
|
||||
for (Bitstream bitstream : bitstreams) {
|
||||
String mimeType = null;
|
||||
try {
|
||||
mimeType = bitstream.getFormat(context).getMIMEType();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
// If the ORIGINAL bundle contains a PDF, assume that it represents the
|
||||
// item and add to rendering. Ignore other mime-types. This convention should
|
||||
// be documented.
|
||||
if (mimeType != null && mimeType.contentEquals("application/pdf")) {
|
||||
String id = BITSTREAM_PATH_PREFIX + "/" + bitstream.getID() + "/content";
|
||||
otherContentGenerator.setIdentifier(id);
|
||||
otherContentGenerator.setLabel(PDF_DOWNLOAD_LABEL);
|
||||
otherContentGenerator.setFormat(mimeType);
|
||||
sequence.addRendering(otherContentGenerator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A link to a service that makes more functionality available for the resource,
|
||||
* such as the base URI of an associated IIIF Search API service.
|
||||
*
|
||||
* This method returns a search service definition. Search scope is the manifest.
|
||||
*
|
||||
* @param item DSpace Item
|
||||
* @return the IIIF search service definition for the item
|
||||
*/
|
||||
private void addSearchService(Item item) {
|
||||
if (utils.isSearchable(item)) {
|
||||
contentSearchGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/manifest/search");
|
||||
// TODO: get label from configuration then set on generator?
|
||||
manifestGenerator.addService(contentSearchGenerator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Ranges to manifest structures element.
|
||||
* Ranges are defined in the info.json file.
|
||||
* @param info
|
||||
* @param identifier
|
||||
*/
|
||||
private void addRanges(Info info, String identifier) {
|
||||
List<RangeModel> rangesFromConfig = utils.getRangesFromInfoObject(info);
|
||||
if (rangesFromConfig != null) {
|
||||
for (int pos = 0; pos < rangesFromConfig.size(); pos++) {
|
||||
setRange(identifier, rangesFromConfig.get(pos), pos);
|
||||
manifestGenerator.addRange(rangeGenerator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets properties on the RangeFacade.
|
||||
* @param identifier DSpace item id
|
||||
* @param range range from info.json configuration
|
||||
* @param pos list position of the range
|
||||
*/
|
||||
private void setRange(String identifier, RangeModel range, int pos) {
|
||||
String id = IIIF_ENDPOINT + identifier + "/r" + pos;
|
||||
String label = range.getLabel();
|
||||
rangeGenerator.setIdentifier(id);
|
||||
rangeGenerator.setLabel(label);
|
||||
String startCanvas = utils.getCanvasId(range.getStart());
|
||||
rangeGenerator.addCanvas(canvasService.getRangeCanvasReference(identifier, startCanvas));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds thumbnail to the manifest
|
||||
* @param bitstreams
|
||||
* @param context
|
||||
*/
|
||||
private void addThumbnail(List<Bitstream> bitstreams, Context context) {
|
||||
if (bitstreams.size() > 0) {
|
||||
String mimeType = utils.getBitstreamMimeType(bitstreams.get(0), context);
|
||||
if (utils.checkImageMimeType(mimeType)) {
|
||||
manifestGenerator.addThumbnail(getThumbnailAnnotation(bitstreams.get(0).getID(), mimeType));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* 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.iiif.service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator;
|
||||
import org.dspace.app.rest.iiif.model.generator.CanvasGenerator;
|
||||
import org.dspace.app.rest.iiif.model.generator.ContentAsTextGenerator;
|
||||
import org.dspace.app.rest.iiif.model.generator.ManifestGenerator;
|
||||
import org.dspace.app.rest.iiif.model.generator.SearchResultGenerator;
|
||||
import org.dspace.app.rest.iiif.service.util.IIIFUtils;
|
||||
import org.dspace.services.ConfigurationService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.annotation.RequestScope;
|
||||
|
||||
/**
|
||||
* Implements IIIF Search API queries and responses.
|
||||
*/
|
||||
@Component
|
||||
@RequestScope
|
||||
public class SearchService extends AbstractResourceService {
|
||||
|
||||
@Autowired
|
||||
IIIFUtils utils;
|
||||
|
||||
@Autowired
|
||||
ContentAsTextGenerator contentAsText;
|
||||
|
||||
@Autowired
|
||||
CanvasGenerator canvas;
|
||||
|
||||
@Autowired
|
||||
AnnotationGenerator annotation;
|
||||
|
||||
@Autowired
|
||||
ManifestGenerator manifest;
|
||||
|
||||
@Autowired
|
||||
SearchResultGenerator searchResult;
|
||||
|
||||
public SearchService(ConfigurationService configurationService) {
|
||||
setConfiguration(configurationService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a search that is scoped to the manifest.
|
||||
*
|
||||
* @param uuid the IIIF manifest uuid
|
||||
* @param query the solr query
|
||||
* @return IIIF json
|
||||
*/
|
||||
public String searchWithinManifest(UUID uuid, String query) {
|
||||
String encodedQuery = URLEncoder.encode(query, StandardCharsets.UTF_8);
|
||||
String json = getSolrSearchResponse(createSearchUrl(encodedQuery, getManifestId(uuid)));
|
||||
return getAnnotationList(json, uuid, encodedQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the Search API solr query.
|
||||
* @param url solr query url
|
||||
* @return json query response
|
||||
*/
|
||||
private String getSolrSearchResponse(URL url) {
|
||||
InputStream jsonStream;
|
||||
String json = null;
|
||||
try {
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("GET");
|
||||
connection.setRequestProperty("Accept", "application/json");
|
||||
jsonStream = connection.getInputStream();
|
||||
json = IOUtils.toString(jsonStream, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a solr search URL.
|
||||
*
|
||||
* @param encodedQuery the search terms
|
||||
* @param manifestId the id of the manifest in which to search
|
||||
* @return solr query
|
||||
*/
|
||||
private URL createSearchUrl(String encodedQuery, String manifestId) {
|
||||
|
||||
String fullQuery = SEARCH_URL + "/select?" +
|
||||
"q=ocr_text:\"" + encodedQuery +
|
||||
"\"%20AND%20manifest_url:\"" + manifestId + "\"" +
|
||||
"&hl=true" +
|
||||
"&hl.ocr.fl=ocr_text" +
|
||||
"&hl.ocr.contextBlock=line" +
|
||||
"&hl.ocr.contextSize=2" +
|
||||
"&hl.snippets=10" +
|
||||
"&hl.ocr.limitBlock=page" +
|
||||
"&hl.ocr.absoluteHighlights=true";
|
||||
try {
|
||||
URL url = new URL(fullQuery);
|
||||
return url;
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException("Malformed query URL", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a Search API response from the word_highlighting solr query response.
|
||||
*
|
||||
* The function assumes that the solr query responses contains page IDs
|
||||
* (taken from the ALTO Page ID element) in the following format:
|
||||
* Page.0, Page.1, Page.2....
|
||||
*
|
||||
* The identifier values must be aligned with zero-based IIIF canvas identifiers:
|
||||
* c0, c1, c2....
|
||||
*
|
||||
* The convention convention for Alto IDs must be followed when indexing ALTO files
|
||||
* into the word_highlighting solr index. If it is not, search responses will not
|
||||
* match canvases.
|
||||
*
|
||||
* @param json solr search result
|
||||
* @param uuid DSpace Item uuid
|
||||
* @param encodedQuery the solr query
|
||||
* @return a search response in JSON
|
||||
*/
|
||||
private String getAnnotationList(String json, UUID uuid, String encodedQuery) {
|
||||
searchResult.setIdentifier(getManifestId(uuid) + "/search?q=" + encodedQuery);
|
||||
GsonBuilder builder = new GsonBuilder();
|
||||
Gson gson = builder.create();
|
||||
JsonObject body = gson.fromJson(json, JsonObject.class);
|
||||
// outer ocr highlight element
|
||||
JsonObject highs = body.getAsJsonObject("ocrHighlighting");
|
||||
// highlight entries
|
||||
for (Map.Entry<String, JsonElement> ocrIds: highs.entrySet()) {
|
||||
// ocr_text
|
||||
JsonObject ocrObj = ocrIds.getValue().getAsJsonObject().getAsJsonObject("ocr_text");
|
||||
// snippets array
|
||||
if (ocrObj != null) {
|
||||
for (JsonElement snippetArray : ocrObj.getAsJsonObject().get("snippets").getAsJsonArray()) {
|
||||
for (JsonElement highlights : snippetArray.getAsJsonObject().getAsJsonArray("highlights")) {
|
||||
for (JsonElement highlight : highlights.getAsJsonArray()) {
|
||||
JsonObject hcoords = highlight.getAsJsonObject();
|
||||
String text = (hcoords.get("text").getAsString());
|
||||
String pageId = getCanvasId((hcoords.get("page").getAsString()));
|
||||
Integer ulx = hcoords.get("ulx").getAsInt();
|
||||
Integer uly = hcoords.get("uly").getAsInt();
|
||||
Integer lrx = hcoords.get("lrx").getAsInt();
|
||||
Integer lry = hcoords.get("lry").getAsInt();
|
||||
String w = Integer.toString(lrx - ulx);
|
||||
String h = Integer.toString(lry - uly);
|
||||
String params = ulx + "," + uly + "," + w + "," + h;
|
||||
AnnotationGenerator annot = createSearchResultAnnotation(params, text, pageId, uuid);
|
||||
searchResult.addResource(annot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return utils.asJson(searchResult.getResource());
|
||||
}
|
||||
|
||||
private String getCanvasId(String altoId) {
|
||||
String[] identArr = altoId.split("\\.");
|
||||
return "c" + identArr[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates annotation with word highlight coordinates.
|
||||
*
|
||||
* @param params word coordinate parameters used for highlighting.
|
||||
* @param text word text
|
||||
* @param pageId the page id returned by solr
|
||||
* @param uuid the dspace item identifier
|
||||
* @return a single annotation object that contains word highlights on a single page (canvas)
|
||||
*/
|
||||
private AnnotationGenerator createSearchResultAnnotation(String params, String text, String pageId, UUID uuid) {
|
||||
annotation.setIdentifier(IIIF_ENDPOINT + uuid + "/annot/" + pageId + "-"
|
||||
+ params);
|
||||
canvas.setIdentifier(IIIF_ENDPOINT + uuid + "/canvas/" + pageId + "#xywh="
|
||||
+ params);
|
||||
annotation.setOnCanvas(canvas);
|
||||
contentAsText.setText(text);
|
||||
annotation.setResource(contentAsText);
|
||||
annotation.setMotivation(AnnotationGenerator.PAINTING);
|
||||
List<ManifestGenerator> withinList = new ArrayList<>();
|
||||
manifest.setIdentifier(getManifestId(uuid));
|
||||
manifest.setLabel("Search within manifest.");
|
||||
withinList.add(manifest);
|
||||
annotation.setWithin(withinList);
|
||||
return annotation;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,314 @@
|
||||
/**
|
||||
* 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.iiif.service.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import de.digitalcollections.iiif.model.sharedcanvas.Resource;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.dspace.app.rest.iiif.model.ObjectMapperFactory;
|
||||
import org.dspace.app.rest.iiif.model.info.Info;
|
||||
import org.dspace.app.rest.iiif.model.info.RangeModel;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
import org.dspace.content.Bitstream;
|
||||
import org.dspace.content.BitstreamFormat;
|
||||
import org.dspace.content.Bundle;
|
||||
import org.dspace.content.Item;
|
||||
import org.dspace.content.MetadataValue;
|
||||
import org.dspace.content.service.BitstreamService;
|
||||
import org.dspace.core.Context;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class IIIFUtils {
|
||||
|
||||
private static final Logger log = Logger.getLogger(IIIFUtils.class);
|
||||
|
||||
// The canvas position will be appended to this string.
|
||||
private static final String CANVAS_PATH_BASE = "/canvas/c";
|
||||
|
||||
// get dbmdz module subclass.
|
||||
protected SimpleModule iiifModule = ObjectMapperFactory.getIiifModule();
|
||||
// Use the dbmdz object mapper subclass.
|
||||
protected ObjectMapper mapper = ObjectMapperFactory.getIiifObjectMapper();
|
||||
|
||||
@Autowired
|
||||
protected BitstreamService bitstreamService;
|
||||
|
||||
/**
|
||||
* For IIIF entities, this method returns the bundle assigned to IIIF
|
||||
* bitstreams. If the item is not an IIIF entity, the default (ORIGINAL)
|
||||
* bundle list is returned instead.
|
||||
* @param item the DSpace item
|
||||
* @param iiifBundle the name of the IIIF bundle
|
||||
* @return DSpace bundle
|
||||
*/
|
||||
public List<Bundle> getIiifBundle(Item item, String iiifBundle) {
|
||||
boolean iiif = item.getMetadata().stream()
|
||||
.filter(m -> m.getMetadataField().toString().contentEquals("relationship_type"))
|
||||
.anyMatch(m -> m.getValue().contentEquals("IIIF") ||
|
||||
m.getValue().contentEquals("IIIFSearchable"));
|
||||
List<Bundle> bundles;
|
||||
if (iiif) {
|
||||
bundles = item.getBundles(iiifBundle);
|
||||
} else {
|
||||
bundles = item.getBundles();
|
||||
}
|
||||
return bundles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the requested bundle.
|
||||
* @param item DSpace item
|
||||
* @param name bundle name
|
||||
* @return
|
||||
*/
|
||||
public List<Bundle> getBundle(Item item, String name) {
|
||||
return item.getBundles(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns bitstreams for the first bundle in the list.
|
||||
* @param bundles list of DSpace bundles
|
||||
* @return list of bitstreams
|
||||
*/
|
||||
public List<Bitstream> getBitstreams(List<Bundle> bundles) {
|
||||
if (bundles == null || bundles.size() == 0) {
|
||||
throw new RuntimeException("Unable to retrieve DSpace bundle for manifest.");
|
||||
}
|
||||
return bundles.get(0).getBitstreams();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bitstream mime type
|
||||
* @param bitstream DSpace bitstream
|
||||
* @param context DSpace context
|
||||
* @return mime type
|
||||
*/
|
||||
public String getBitstreamMimeType(Bitstream bitstream, Context context) {
|
||||
try {
|
||||
BitstreamFormat bitstreamFormat = bitstream.getFormat(context);
|
||||
return bitstreamFormat.getMIMEType();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the item is searchable. Based on the entity type.
|
||||
* @param item DSpace item
|
||||
* @return true if searchable
|
||||
*/
|
||||
public boolean isSearchable(Item item) {
|
||||
return item.getMetadata().stream()
|
||||
.filter(m -> m.getMetadataField().toString().contentEquals("relationship_type"))
|
||||
.anyMatch(m -> m.getValue().contentEquals("IIIFSearchable"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a metadata field name.
|
||||
* @param meta the DSpace metadata value object
|
||||
* @return field name as string
|
||||
*/
|
||||
public String getMetadataFieldName(MetadataValue meta) {
|
||||
String element = meta.getMetadataField().getElement();
|
||||
String qualifier = meta.getMetadataField().getQualifier();
|
||||
// Need to distinguish DC type from DSpace relationship.type.
|
||||
// Setting element to be the schema name.
|
||||
if (meta.getMetadataField().getMetadataSchema().getName().contentEquals("relationship")) {
|
||||
qualifier = element;
|
||||
element = meta.getMetadataField().getMetadataSchema().getName();
|
||||
}
|
||||
String field = element;
|
||||
// Add qualifier if defined.
|
||||
if (qualifier != null) {
|
||||
field = field + "." + qualifier;
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrives a bitstream based on its position in the IIIF bundle.
|
||||
* @param item DSpace Item
|
||||
* @param canvasPosition bitstream position
|
||||
* @return bitstream
|
||||
*/
|
||||
public Bitstream getBitstreamForCanvas(Item item, String bundleName, int canvasPosition) {
|
||||
List<Bundle> bundles = item.getBundles(bundleName);
|
||||
if (bundles.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
List<Bitstream> bitstreams = bundles.get(0).getBitstreams();
|
||||
try {
|
||||
return bitstreams.get(canvasPosition);
|
||||
} catch (RuntimeException e) {
|
||||
throw new RuntimeException("The requested canvas is not available", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to find info.json file in the bitstream bundle and convert
|
||||
* the json into the Info.class domain model for canvas and range parameters.
|
||||
* @param context DSpace context
|
||||
* @param bundleName the IIIF bundle
|
||||
* @return info domain model
|
||||
*/
|
||||
public Info getInfo(Context context, Item item, String bundleName) {
|
||||
Info info = null;
|
||||
try {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
// Look for expected json file bitstream in bundle.
|
||||
Bitstream infoBitstream = bitstreamService
|
||||
.getBitstreamByName(item, bundleName, "info.json");
|
||||
if (infoBitstream != null) {
|
||||
InputStream is = bitstreamService.retrieve(context, infoBitstream);
|
||||
info = mapper.readValue(is, Info.class);
|
||||
}
|
||||
} catch (IOException | SQLException e) {
|
||||
log.warn("Unable to read info.json file.", e);
|
||||
} catch (AuthorizeException e) {
|
||||
log.warn("Not authorized to access info.json file.", e);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the range parameter List or null
|
||||
* @param info the parameters model
|
||||
* @return list of range models
|
||||
*/
|
||||
public List<RangeModel> getRangesFromInfoObject(Info info) {
|
||||
if (info != null) {
|
||||
return info.getStructures();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts canvas position from the URL input path.
|
||||
* @param canvasId e.g. "c12"
|
||||
* @return the position, e.g. 12
|
||||
*/
|
||||
public int getCanvasId(String canvasId) {
|
||||
return Integer.parseInt(canvasId.substring(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the canvas path with position. The path
|
||||
* returned is partial, not the fully qualified URI.
|
||||
* @param position position of the bitstream in the DSpace bundle.
|
||||
* @return partial canvas path.
|
||||
*/
|
||||
public String getCanvasId(int position) {
|
||||
return CANVAS_PATH_BASE + position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to compare canvas parameter and bitstream list size.
|
||||
* @param info the parameter model
|
||||
* @param bitstreams the list of DSpace bitstreams
|
||||
* @return true if sizes match
|
||||
*/
|
||||
public boolean isListSizeMatch(Info info, List<Bitstream> bitstreams) {
|
||||
// If Info is not null then the bitstream bundle contains info.json; exclude
|
||||
// the file from comparison.
|
||||
if (info != null && info.getCanvases().size() == bitstreams.size() - 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method verifies that the requested canvas exists in the
|
||||
* parameters model object.
|
||||
* @param info parameter model
|
||||
* @param canvasPosition requested canvas position
|
||||
* @return true if index is in bounds
|
||||
*/
|
||||
public boolean canvasOutOfBounds(Info info, int canvasPosition) {
|
||||
return canvasPosition < 0 || canvasPosition >= info.getCanvases().size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates info.json for a single canvas.
|
||||
* Unless global settings are being used, when canvas information is not available
|
||||
* use defaults. The canvas information is defined in the info.json file.
|
||||
* @param info the information model
|
||||
* @param position the position of the requested canvas
|
||||
* @return information model
|
||||
*/
|
||||
public Info validateInfoForSingleCanvas(Info info, int position) {
|
||||
if (info != null && info.getGlobalDefaults() != null) {
|
||||
if (canvasOutOfBounds(info, position) && !info.getGlobalDefaults().isActivated()) {
|
||||
log.warn("Canvas for position " + position + " not defined.\n" +
|
||||
"Ignoring info.json canvas definitions and using defaults. " +
|
||||
"Any other canvas-level annotations will also be ignored.");
|
||||
info.setCanvases(new ArrayList<>());
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unless global settings are being used, when canvas information list size does
|
||||
* not match the number of bitstreams use defaults. The canvas information is
|
||||
* defined in the info.json file.
|
||||
* @param info the information model
|
||||
* @param bitstreams the list of bitstreams
|
||||
* @return information model
|
||||
*/
|
||||
public Info validateInfoForManifest(Info info, List<Bitstream> bitstreams) {
|
||||
if (info != null && info.getGlobalDefaults() != null) {
|
||||
if (!isListSizeMatch(info, bitstreams) && !info.getGlobalDefaults().isActivated()) {
|
||||
log.warn("Mismatch between info.json canvases and DSpace bitstream count.\n" +
|
||||
"Ignoring info.json canvas definitions and using defaults." +
|
||||
"Any other canvas-level annotations will also be ignored.");
|
||||
info.setCanvases(new ArrayList<>());
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the json response.
|
||||
* @param resource to be serialized
|
||||
* @return
|
||||
*/
|
||||
public String asJson(Resource<?> resource) {
|
||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
mapper.registerModule(iiifModule);
|
||||
try {
|
||||
return mapper.writeValueAsString(resource);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for image mimetype. Presentation API 2.1.1 canvas supports images only.
|
||||
* Other media types introduced in version 3.
|
||||
* @param mimetype
|
||||
* @return true if an image
|
||||
*/
|
||||
public boolean checkImageMimeType(String mimetype) {
|
||||
if (mimetype != null && mimetype.contains("image/")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.rest.iiif.service.util;
|
||||
|
||||
import org.dspace.app.rest.iiif.model.generator.ProfileGenerator;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class ImageProfileUtil {
|
||||
|
||||
@Autowired
|
||||
ProfileGenerator profile;
|
||||
|
||||
/**
|
||||
* Utility method for obtaining the image service profile.
|
||||
* Calling from this utility provides a unique instance of the
|
||||
* autowired property. Necessary because a single canvas resource contains
|
||||
* both thumbnail and images.
|
||||
*
|
||||
* @return image service profile
|
||||
*/
|
||||
public ProfileGenerator getImageProfile() throws
|
||||
RuntimeException {
|
||||
profile.setIdentifier("http://iiif.io/api/image/2/level1.json");
|
||||
return profile;
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
package org.dspace.app.rest.iiif.service.util;
|
||||
|
||||
import org.dspace.app.rest.iiif.model.generator.ProfileGenerator;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class ThumbProfileUtil {
|
||||
|
||||
@Autowired
|
||||
ProfileGenerator profile;
|
||||
|
||||
/**
|
||||
* Utility method for obtaining the thumbnail image service profile.
|
||||
* Calling from this utility provides a unique instance of the
|
||||
* autowired property. Necessary because a single canvas resource contains
|
||||
* both thumbnail and images.
|
||||
*
|
||||
* @return the thumbnail service profile
|
||||
*/
|
||||
public ProfileGenerator getThumbnailProfile() throws
|
||||
RuntimeException {
|
||||
profile.setIdentifier("http://iiif.io/api/image/2/level0.json");
|
||||
return profile;
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user