Intermediate commit - refactoring to rely on metadata for feature activation and configuration

This commit is contained in:
Andrea Bollini
2021-09-22 18:00:29 +02:00
parent 276fe2297f
commit d25c74f001
28 changed files with 652 additions and 724 deletions

View File

@@ -136,6 +136,27 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder<Bitstream> {
return this;
}
public BitstreamBuilder withIIIFLabel(String label) throws SQLException {
bitstreamService.addMetadata(context, bitstream, "iiif", "label", null, null, label);
return this;
}
public BitstreamBuilder withIIIFCanvasWidth(int i) throws SQLException {
bitstreamService.addMetadata(context, bitstream, "iiif", "image", "width", null, String.valueOf(i));
return this;
}
public BitstreamBuilder withIIIFCanvasHeight(int i) throws SQLException {
bitstreamService.addMetadata(context, bitstream, "iiif", "image", "height", null, String.valueOf(i));
return this;
}
public BitstreamBuilder withIIIFToC(String toc) throws SQLException {
bitstreamService.addMetadata(context, bitstream, "iiif", "toc", null, null, toc);
return this;
}
private Bundle getOriginalBundle(Item item) throws SQLException, AuthorizeException {
List<Bundle> bundles = itemService.getBundles(item, ORIGINAL);
Bundle targetBundle = null;
@@ -199,4 +220,5 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder<Bitstream> {
protected DSpaceObjectService<Bitstream> getService() {
return bitstreamService;
}
}

View File

@@ -115,6 +115,30 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder<Item> {
return addMetadataValue(item, MetadataSchemaEnum.DC.getName(), "description", "provenance", provenanceData);
}
public ItemBuilder enableIIIF() {
return addMetadataValue(item, "dspace", "iiif", "enabled", "true");
}
public ItemBuilder disableIIIF() {
return addMetadataValue(item, "dspace", "iiif", "enabled", "false");
}
public ItemBuilder enableIIIFSearch() {
return addMetadataValue(item, "iiif", "search", "enabled", "true");
}
public ItemBuilder withIIIFCanvasLabel(String label) {
return addMetadataValue(item, "iiif", "canvas", "label", label);
}
public ItemBuilder withIIIFCanvasWidth(int i) {
return addMetadataValue(item, "iiif", "image", "width", String.valueOf(i));
}
public ItemBuilder withIIIFCanvasHeight(int i) {
return addMetadataValue(item, "iiif", "image", "height", String.valueOf(i));
}
public ItemBuilder withMetadata(final String schema, final String element, final String qualifier,
final String value) {
return addMetadataValue(item, schema, element, qualifier, value);
@@ -220,4 +244,5 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder<Item> {
c.complete();
}
}
}

View File

@@ -14,6 +14,7 @@ import org.dspace.app.rest.iiif.service.AnnotationListService;
import org.dspace.app.rest.iiif.service.CanvasLookupService;
import org.dspace.app.rest.iiif.service.ManifestService;
import org.dspace.app.rest.iiif.service.SearchService;
import org.dspace.app.rest.iiif.service.util.IIIFUtils;
import org.dspace.content.Item;
import org.dspace.content.service.BitstreamService;
import org.dspace.content.service.ItemService;
@@ -48,6 +49,8 @@ public class IIIFServiceFacade {
@Autowired
CanvasLookupService canvasLookupService;
@Autowired
IIIFUtils utils;
/**
* The manifest response contains sufficient information for the client to initialize itself
@@ -71,7 +74,7 @@ public class IIIFServiceFacade {
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e);
}
if (item == null) {
if (item == null || !utils.isIIIFEnabled(item)) {
throw new ResourceNotFoundException("IIIF manifest for id " + id + " not found");
}
return manifestService.getManifest(item, context);

View File

@@ -34,6 +34,10 @@ public class CanvasGenerator implements IIIFResource {
return this;
}
public String getIdentifier() {
return identifier;
}
/**
* Every canvas must have a label to display.
* @param label

View File

@@ -61,8 +61,10 @@ public class CanvasItemsGenerator implements org.dspace.app.rest.iiif.model.gene
* Add a Canvas to the sequence.
* @param canvas wrapper for Canvas
*/
public void addCanvas(org.dspace.app.rest.iiif.model.generator.CanvasGenerator canvas) {
this.canvas.add((Canvas) canvas.getResource());
public String addCanvas(org.dspace.app.rest.iiif.model.generator.CanvasGenerator canvas) {
Canvas resource = (Canvas) canvas.getResource();
this.canvas.add(resource);
return resource.getIdentifier().toString();
}
@Override

View File

@@ -132,9 +132,9 @@ public class ManifestGenerator implements IIIFResource {
* @param field property field
* @param value property value
*/
public void addMetadata(String field, String value) {
public void addMetadata(String field, String value, String... rest) {
metadataEntryGenerator.setField(field);
metadataEntryGenerator.setValue(value);
metadataEntryGenerator.setValue(value, rest);
metadata.add(metadataEntryGenerator.getValue());
}
@@ -148,19 +148,18 @@ public class ManifestGenerator implements IIIFResource {
/**
* Adds optional description to Manifest.
* @param field property field
* @param value property value
* @param value the description value
*/
public void addDescription(String field, String value) {
description = new PropertyValueGenerator().getPropertyValue(field, value).getValue();
public void addDescription(String value) {
description = new PropertyValueGenerator().getPropertyValue(value).getValue();
}
/**
* Adds optional Range to the manifest's structures element.
* @param rangeGenerator list of range models
* @param rangeGenerator to add
*/
public void setRange(List<RangeGenerator> rangeGenerator) {
ranges = rangeGenerator;
public void addRange(RangeGenerator rangeGenerator) {
ranges.add(rangeGenerator);
}
@Override

View File

@@ -8,6 +8,7 @@
package org.dspace.app.rest.iiif.model.generator;
import de.digitalcollections.iiif.model.MetadataEntry;
import de.digitalcollections.iiif.model.PropertyValue;
import org.springframework.stereotype.Component;
@Component
@@ -15,6 +16,7 @@ public class MetadataEntryGenerator implements IIIFValue {
private String field;
private String value;
private String[] rest;
/**
* Set metadata field name.
@@ -28,12 +30,19 @@ public class MetadataEntryGenerator implements IIIFValue {
* Set metadata value.
* @param value metadata value
*/
public void setValue(String value) {
public void setValue(String value, String... rest) {
this.value = value;
this.rest = rest;
}
@Override
public MetadataEntry getValue() {
return new MetadataEntry(field, value);
PropertyValue metadataValues;
if (rest != null && rest.length > 0) {
metadataValues = new PropertyValue(value, rest);
} else {
metadataValues = new PropertyValue(value);
}
return new MetadataEntry(new PropertyValue(field), metadataValues);
}
}

View File

@@ -20,14 +20,15 @@ import de.digitalcollections.iiif.model.sharedcanvas.Resource;
* The rationale for separating ranges from sequences is that there is likely to be overlap between different ranges,
* such as the physical structure of a book compared to the textual structure of the work.
*
* This is used to populate the "structures" element of the Manifest. (The REST API service looks to the "info.json"
* file for ranges.)
* This is used to populate the "structures" element of the Manifest. The structure is derived from the iiif.toc
* metadata and the ordered sequence of bitstreams (canvases)
*/
public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFResource {
private String identifier;
private String label;
private final List<Canvas> canvasList = new ArrayList<>();
private final List<RangeGenerator> rangesList = new ArrayList<>();
/**
* Sets mandatory range identifier.
@@ -38,6 +39,10 @@ public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator.
return this;
}
public String getIdentifier() {
return identifier;
}
/**
* Sets mandatory range label.
* @param label range label
@@ -62,6 +67,14 @@ public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator.
for (Canvas canvas : canvasList) {
range.addCanvas(canvas);
}
for (RangeGenerator rg : rangesList) {
range.addRange((Range) rg.getResource());
}
return range;
}
public void addSubRange(RangeGenerator range) {
range.setIdentifier(identifier + "-" + rangesList.size());
rangesList.add(range);
}
}

View File

@@ -1,30 +0,0 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.iiif.model.info;
public class Annotation {
private String motivation;
private String id;
public void setMotivation(String motivation) {
this.motivation = motivation;
}
public String getMotivation() {
return motivation;
}
public void setID(String id) {
this.id = id;
}
public String getId() {
return id;
}
}

View File

@@ -1,50 +0,0 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.iiif.model.info;
public class Canvas {
private String label;
private int width;
private int height;
private int pos;
public void setLabel(String label) {
this.label = label;
}
public String getLabel() {
return label;
}
public void setWidth(int width) {
this.width = width;
}
public int getWidth() {
return width;
}
public void setHeight(int height) {
this.height = height;
}
public int getHeight() {
return height;
}
// TODO: These can be removed.
public void setPos(int pos) {
this.pos = pos;
}
public int getPos() {
return pos;
}
}

View File

@@ -1,51 +0,0 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.iiif.model.info;
public class GlobalDefaults {
private boolean activated;
private String label;
private int width;
private int height;
public boolean isActivated() {
return activated;
}
public void setActivated(boolean activated) {
this.activated = activated;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}

View File

@@ -1,42 +0,0 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.iiif.model.info;
import java.util.List;
public class Info {
private List<Canvas> canvases;
private List<Range> structures;
private GlobalDefaults globalDefaults;
public GlobalDefaults getGlobalDefaults() {
return globalDefaults;
}
public void setGlobalDefaults(GlobalDefaults globalDefaults) {
this.globalDefaults = globalDefaults;
}
public void setCanvases(List<Canvas> canvases) {
this.canvases = canvases;
}
public List<Canvas> getCanvases() {
return this.canvases;
}
public void setStructures(List<Range> structures) {
this.structures = structures;
}
public List<Range> getStructures() {
return structures;
}
}

View File

@@ -1,31 +0,0 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.iiif.model.info;
public class Range {
private String label;
private int start;
public void setLabel(String label) {
this.label = label;
}
public String getLabel() {
return label;
}
public void setStart(int start) {
this.start = start;
}
public int getStart() {
return start;
}
}

View File

@@ -28,20 +28,20 @@ public abstract class AbstractResourceService {
protected String CLIENT_URL;
protected String IIIF_LOGO_IMAGE;
protected String BITSTREAM_PATH_PREFIX;
protected int DEFAULT_CANVAS_WIDTH;
protected int DEFAULT_CANVAS_HEIGHT;
/**
* Possible values: "paged" or "individuals". The property
* value is set in dspace configuration.
*/
protected static String DOCUMENT_VIEWING_HINT;
// 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";
// Default canvas dimensions.
protected static final Integer DEFAULT_CANVAS_WIDTH_FALLBACK = 1200;
protected static final Integer DEFAULT_CANVAS_HEIGHT_FALLBACK = 1600;
@Autowired
IIIFUtils utils;
@@ -65,6 +65,10 @@ public abstract class AbstractResourceService {
DOCUMENT_VIEWING_HINT = configurationService.getProperty("iiif.document.viewing.hint");
CLIENT_URL = configurationService.getProperty("dspace.ui.url");
IIIF_LOGO_IMAGE = configurationService.getProperty("iiif.logo.image");
DEFAULT_CANVAS_WIDTH = configurationService.getIntProperty("iiif.canvas.default-width",
DEFAULT_CANVAS_WIDTH_FALLBACK);
DEFAULT_CANVAS_HEIGHT = configurationService.getIntProperty("iiif.canvas.default-heigth",
DEFAULT_CANVAS_HEIGHT_FALLBACK);
}
/**

View File

@@ -17,7 +17,6 @@ import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator;
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;
@@ -84,28 +83,23 @@ public class AnnotationListService extends AbstractResourceService {
// AnnotationList requires an identifier.
annotationList.setIdentifier(IIIF_ENDPOINT + id + "/manifest/seeAlso");
// Get the "OtherContent" bundle for the item. 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);
}
AnnotationGenerator annotation = applicationContext
.getBean(AnnotationGenerator.class, IIIF_ENDPOINT + bitstream.getID()
+ "/annot", AnnotationGenerator.LINKING);
annotation.setResource(getLinksGenerator(mimetype, bitstream));
annotationList.addResource(annotation);
}
// Get the "seeAlso" bitstreams for the item. Add
// Annotations for each bitstream found.
List<Bitstream> bitstreams = utils.getSeeAlsoBitstreams(item);
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);
}
AnnotationGenerator annotation = applicationContext
.getBean(AnnotationGenerator.class, IIIF_ENDPOINT + bitstream.getID()
+ "/annot", AnnotationGenerator.LINKING);
annotation.setResource(getLinksGenerator(mimetype, bitstream));
annotationList.addResource(annotation);
}
return utils.asJson(annotationList.getResource());
}

View File

@@ -7,10 +7,9 @@
*/
package org.dspace.app.rest.iiif.service;
import java.util.UUID;
import java.sql.SQLException;
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;
@@ -40,17 +39,18 @@ public class CanvasLookupService extends AbstractResourceService {
public String generateCanvas(Context context, Item item, String canvasId) {
int canvasPosition = utils.getCanvasId(canvasId);
Bitstream bitstream = utils.getBitstreamForCanvas(item, IIIF_BUNDLE, canvasPosition);
Bitstream bitstream = utils.getBitstreamForCanvas(context, item, canvasPosition);
if (bitstream == null) {
throw new ResourceNotFoundException();
}
Info info =
utils.validateInfoForSingleCanvas(utils.getInfo(context, item, IIIF_BUNDLE), canvasPosition);
UUID bitstreamId = bitstream.getID();
String mimeType = utils.getBitstreamMimeType(bitstream, context);
CanvasGenerator canvasGenerator =
canvasService.getCanvas(item.getID().toString(), bitstreamId, mimeType, info, canvasPosition);
CanvasGenerator canvasGenerator;
try {
canvasGenerator = canvasService.getCanvas(item.getID().toString(), bitstream, bitstream.getBundles().get(0),
item, canvasPosition, mimeType);
} catch (SQLException e) {
throw new RuntimeException(e);
}
return utils.asJson(canvasGenerator.getResource());
}

View File

@@ -12,7 +12,10 @@ import java.util.UUID;
import org.apache.logging.log4j.Logger;
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.info.Info;
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.services.ConfigurationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
@@ -24,15 +27,15 @@ public class CanvasService extends AbstractResourceService {
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(CanvasService.class);
// Default canvas dimensions.
protected static final Integer DEFAULT_CANVAS_WIDTH = 1200;
protected static final Integer DEFAULT_CANVAS_HEIGHT = 1600;
@Autowired
ImageContentService imageContentService;
@Autowired
IIIFUtils utils;
/**
* Constructor.
*
* @param configurationService the DSpace configuration service.
*/
public CanvasService(ConfigurationService configurationService) {
@@ -40,67 +43,44 @@ public class CanvasService extends AbstractResourceService {
}
/**
* Creates a single Canvas object. If canvas parameters are provided by the
* Info object they are used. If canvas parameters are unavailable, default values
* 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.
*
* Note that info.json is going to be replaced with metadata in the bitstream DSO.
* Note that info.json is going to be replaced with metadata in the bitstream
* DSO.
*
* @param manifestId manifest id
* @param manifestId manifest id
* @param bitstreamId uuid of the bitstream
* @param mimeType the mimetype of the bitstream
* @param info parameters for this canvas
* @param count the canvas position in the sequence.
* @param mimeType the mimetype of the bitstream
* @param info parameters for this canvas
* @param count the canvas position in the sequence.
* @return canvas object
*/
protected CanvasGenerator getCanvas(String manifestId, UUID bitstreamId, String mimeType, Info info, int count) {
protected CanvasGenerator getCanvas(String manifestId, Bitstream bitstream, Bundle bundle, Item item, int count,
String mimeType) {
int pagePosition = count + 1;
// Defaults settings. Used if no info.json is provided.
String label = "Page " + pagePosition;
int canvasWidth = DEFAULT_CANVAS_WIDTH;
int canvasHeight = DEFAULT_CANVAS_HEIGHT;
String label = utils.getIIIFLabel(bitstream, "Page " + pagePosition);
int canvasWidth = utils.getCanvasWidth(bitstream, bundle, item, DEFAULT_CANVAS_WIDTH);
int canvasHeight = utils.getCanvasHeight(bitstream, bundle, item, DEFAULT_CANVAS_HEIGHT);
UUID bitstreamId = bitstream.getID();
// Override with settings from info.json, if available.
if (info != null && info.getGlobalDefaults() != null && info.getCanvases() != null) {
// The info.json file can request global defaults for canvas
// height, width and labels. Use global settings if activated in info.json.
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) {
// Labels assumed unique and are not incremented
// when info.json provides individual canvas metadata.
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.");
}
ImageContentGenerator image = imageContentService.getImageContent(bitstreamId, mimeType,
imageUtil.getImageProfile(), IMAGE_PATH);
ImageContentGenerator image = imageContentService
.getImageContent(bitstreamId, mimeType, imageUtil.getImageProfile(), IMAGE_PATH);
ImageContentGenerator thumb = imageContentService
.getImageContent(bitstreamId, mimeType, thumbUtil.getThumbnailProfile(), THUMBNAIL_PATH);
ImageContentGenerator thumb = imageContentService.getImageContent(bitstreamId, mimeType,
thumbUtil.getThumbnailProfile(), THUMBNAIL_PATH);
return new CanvasGenerator().setIdentifier(IIIF_ENDPOINT + manifestId + "/canvas/c" + count)
.addImage(image.getResource())
.addThumbnail(thumb.getResource())
.setHeight(canvasHeight)
.setWidth(canvasWidth)
.setLabel(label);
.addImage(image.getResource()).addThumbnail(thumb.getResource()).setHeight(canvasHeight)
.setWidth(canvasWidth).setLabel(label);
}
/**
* Ranges expect the Canvas object to have only an identifier.
* @param identifier the DSpace item identifier
*
* @param identifier the DSpace item identifier
* @param startCanvas the position of the canvas in list
* @return
*/

View File

@@ -7,7 +7,6 @@
*/
package org.dspace.app.rest.iiif.service;
import java.util.Date;
import java.util.UUID;
import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator;
@@ -23,7 +22,6 @@ public class ImageContentService extends AbstractResourceService {
public ImageContentService(ConfigurationService configurationService) {
System.out.println("ImageContentService " + new Date().toString());
setConfiguration(configurationService);
}

View File

@@ -7,16 +7,21 @@
*/
package org.dspace.app.rest.iiif.service;
import java.util.Date;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.app.rest.iiif.model.generator.CanvasGenerator;
import org.dspace.app.rest.iiif.model.generator.ContentSearchGenerator;
import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator;
import org.dspace.app.rest.iiif.model.generator.ManifestGenerator;
import org.dspace.app.rest.iiif.model.info.Info;
import org.dspace.app.rest.iiif.model.info.Range;
import org.dspace.app.rest.iiif.model.generator.RangeGenerator;
import org.dspace.app.rest.iiif.service.util.IIIFUtils;
import org.dspace.app.util.service.MetadataExposureService;
import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.Item;
@@ -67,13 +72,17 @@ public class ManifestService extends AbstractResourceService {
@Autowired
ManifestGenerator manifestGenerator;
@Autowired
MetadataExposureService metadataExposureService;
protected String[] METADATA_FIELDS;
/**
* Constructor.
* @param configurationService the DSpace configuration service.
*/
public ManifestService(ConfigurationService configurationService) {
setConfiguration(configurationService);
METADATA_FIELDS = configurationService.getArrayProperty("iiif.metadata.item");
}
/**
@@ -98,59 +107,125 @@ public class ManifestService extends AbstractResourceService {
private void populateManifest(Item item, Context context) {
// If an IIIF bundle is found it will be used. Otherwise,
// images in the ORIGINAL bundle will be used.
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()));
List<Bundle> bundles = utils.getIiifBundles(item);
String manifestId = getManifestId(item.getID());
manifestGenerator.setIdentifier(manifestId);
manifestGenerator.setLabel(item.getName());
setLogoContainer();
addRelated(item);
addSearchService(item);
addMetadata(item.getMetadata());
addViewingHint(bitstreams.size());
addThumbnail(bundles, context);
addSequence(item, bitstreams, context, info);
addRanges(info, item.getID().toString());
addMetadata(context, item);
addViewingHint(item);
addThumbnail(item, context);
// List<RangeGenerator> seqs = new ArrayList<RangeGenerator>();
RangeGenerator root = new RangeGenerator();
root.setLabel("Table of Contents");
root.setIdentifier(manifestId + "/range/r0");
// seqs.add(root);
manifestGenerator.addRange(root);
Map<String, RangeGenerator> tocRanges = new HashMap<String, RangeGenerator>();
for (Bundle bnd : bundles) {
String bundleToCPrefix = utils.getIIIFFirstToC(bnd);
RangeGenerator lastRange = root;
for (Bitstream b : utils.getIiifBitstreams(context, bnd)) {
CanvasGenerator canvasId = sequenceService.addCanvas(context, item, bnd, b);
List<String> tocs = utils.getIIIFToCs(b, bundleToCPrefix);
if (tocs.size() > 0) {
for (String toc : tocs) {
RangeGenerator currRange = root;
String[] parts = toc.split(IIIFUtils.TOC_SEPARATOR_REGEX);
String key = "";
for (int pIdx = 0; pIdx < parts.length; pIdx++) {
if (pIdx > 0) {
key += IIIFUtils.TOC_SEPARATOR;
}
key += parts[pIdx];
if (tocRanges.get(key) != null) {
currRange = tocRanges.get(key);
} else {
// create the sub range
RangeGenerator range = new RangeGenerator();
range.setLabel(parts[pIdx]);
range.addCanvas(
canvasService.getRangeCanvasReference(manifestId, canvasId.getIdentifier()));
// add the range reference to the currRange so to get an identifier
currRange.addSubRange(rangeService.getRangeReference(range));
// add the range to the manifest
manifestGenerator.addRange(range);
// move the current range
currRange = range;
tocRanges.put(key, range);
}
}
// add the bitstream canvas to the currRange
currRange.addCanvas(canvasId);
lastRange = currRange;
}
} else {
lastRange.addCanvas(canvasService.getRangeCanvasReference(manifestId, canvasId.getIdentifier()));
}
}
}
manifestGenerator.addSequence(
sequenceService.getSequence(item, context));
//manifestGenerator.setRange(rangeService.getRanges(info, manifestId));
addSeeAlso(item);
}
/**
* Adds a single sequence with canvases and a rendering property (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) {
// After replacing the info object with DSO metadata we might
// update this method to iterate over the bitstreams list, passing
// the individual bitstream and position to revised methods in
// sequenceService and rangeService. But it's hard to try now without more
// work elsewhere.
manifestGenerator.addSequence(
sequenceService.getSequence(item, bitstreams, context, info));
}
/**
* Adds DSpace Item metadata to the manifest.
*
* @param metadata list of DSpace metadata values
*
* @param context the DSpace Context
* @param item the DSpace item
*/
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());
private void addMetadata(Context context, Item item) {
for (String field : METADATA_FIELDS) {
String[] eq = field.split("\\.");
String schema = eq[0];
String element = eq[1];
String qualifier = null;
if (eq.length > 2) {
qualifier = eq[2];
}
List<MetadataValue> metadata = item.getItemService().getMetadata(item, schema, element, qualifier,
Item.ANY);
List<String> values = new ArrayList<String>();
for (MetadataValue meta : metadata) {
// we need to perform the check here as the configuration can include jolly
// characters (i.e. dc.description.*) and we need to be sure to hide qualified
// metadata (dc.description.provenance)
try {
if (metadataExposureService.isHidden(context, meta.getMetadataField().getMetadataSchema().getName(),
meta.getMetadataField().getElement(), meta.getMetadataField().getQualifier())) {
continue;
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
values.add(meta.getValue());
}
if (values.size() > 0) {
if (values.size() > 1) {
manifestGenerator.addMetadata(field, values.get(0),
values.subList(1, values.size()).toArray(new String[values.size() - 1]));
} else {
manifestGenerator.addMetadata(field, values.get(0));
}
}
String descrValue = item.getItemService().getMetadataFirstValue(item, "dc", "description", null, Item.ANY);
if (StringUtils.isNotBlank(descrValue)) {
manifestGenerator.addDescription(descrValue);
}
String licenseUriValue = item.getItemService().getMetadataFirstValue(item, "dc", "rights", "uri", Item.ANY);
if (StringUtils.isNotBlank(licenseUriValue)) {
manifestGenerator.addLicense(licenseUriValue);
}
}
}
@@ -172,12 +247,10 @@ public class ManifestService extends AbstractResourceService {
/**
* A hint to the client as to the most appropriate method of displaying the resource.
*
* @param bitstreamCount count of bitstreams in the IIIF bundle.
* @param item the DSpace Item
*/
private void addViewingHint(int bitstreamCount) {
if (bitstreamCount > 2) {
manifestGenerator.addViewingHint(DOCUMENT_VIEWING_HINT);
}
private void addViewingHint(Item item) {
manifestGenerator.addViewingHint(utils.getIIIFViewingHint(item, DOCUMENT_VIEWING_HINT));
}
/**
@@ -192,10 +265,7 @@ public class ManifestService extends AbstractResourceService {
* @param item the DSpace Item.
*/
private void addSeeAlso(Item item) {
List<Bundle> bundles = utils.getBundle(item, OTHER_CONTENT_BUNDLE);
if (bundles.size() > 0) {
manifestGenerator.addSeeAlso(seeAlsoService.getSeeAlso(item, bundles));
}
manifestGenerator.addSeeAlso(seeAlsoService.getSeeAlso(item));
}
/**
@@ -215,33 +285,18 @@ public class ManifestService extends AbstractResourceService {
}
/**
* 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<Range> rangesFromConfig = utils.getRangesFromInfoObject(info);
if (rangesFromConfig != null) {
manifestGenerator.setRange(rangeService.getRanges(info, identifier));
}
}
/**
* Adds thumbnail to the manifest. Uses first image in bundle.
* @param bundles image bundles
* Adds thumbnail to the manifest. Uses first image in the manifest.
* @param item the DSpace Item
* @param context DSpace context
*/
private void addThumbnail(List<Bundle> bundles, Context context) {
List<Bitstream> bitstreams = utils.getBitstreams(bundles);
private void addThumbnail(Item item, Context context) {
List<Bitstream> bitstreams = utils.getIiifBitstreams(context, item);
if (bitstreams != null && bitstreams.size() > 0) {
String mimeType = utils.getBitstreamMimeType(bitstreams.get(0), context);
if (utils.checkImageMimeType(mimeType)) {
ImageContentGenerator image = imageContentService
.getImageContent(bitstreams.get(0).getID(), mimeType,
thumbUtil.getThumbnailProfile(), THUMBNAIL_PATH);
manifestGenerator.addThumbnail(image);
}
ImageContentGenerator image = imageContentService
.getImageContent(bitstreams.get(0).getID(), mimeType,
thumbUtil.getThumbnailProfile(), THUMBNAIL_PATH);
manifestGenerator.addThumbnail(image);
}
}

View File

@@ -7,12 +7,7 @@
*/
package org.dspace.app.rest.iiif.service;
import java.util.ArrayList;
import java.util.List;
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.Range;
import org.dspace.services.ConfigurationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -30,36 +25,12 @@ public class RangeService extends AbstractResourceService {
}
/**
* Adds Ranges to manifest structures element.
* Ranges are defined in the info.json file.
* @param info
* @param identifier
* Ranges expect the Sub range object to have only an identifier.
*
* @param range the sub range to reference
* @return RangeGenerator able to create the reference
*/
public List<RangeGenerator> getRanges(Info info, String identifier) {
List<RangeGenerator> ranges = new ArrayList<>();
List<Range> rangesFromConfig = utils.getRangesFromInfoObject(info);
if (rangesFromConfig != null) {
for (int pos = 0; pos < rangesFromConfig.size(); pos++) {
ranges.add(getRange(identifier, rangesFromConfig.get(pos), pos));
}
}
return ranges;
}
/**
* Sets properties on the Range.
* @param identifier DSpace item id
* @param range range from info.json configuration
* @param pos list position of the range
*/
private RangeGenerator getRange(String identifier, Range range, int pos) {
String id = IIIF_ENDPOINT + identifier + "/r" + pos;
String label = range.getLabel();
RangeGenerator rangeGenerator = new RangeGenerator();
rangeGenerator.setIdentifier(id);
rangeGenerator.setLabel(label);
String startCanvas = utils.getCanvasId(range.getStart());
rangeGenerator.addCanvas(canvasService.getRangeCanvasReference(identifier, startCanvas));
return rangeGenerator;
public RangeGenerator getRangeReference(RangeGenerator range) {
return new RangeGenerator().setIdentifier(range.getIdentifier());
}
}

View File

@@ -7,11 +7,8 @@
*/
package org.dspace.app.rest.iiif.service;
import java.util.List;
import org.dspace.app.rest.iiif.model.generator.AnnotationGenerator;
import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator;
import org.dspace.content.Bundle;
import org.dspace.content.Item;
import org.dspace.services.ConfigurationService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -31,7 +28,7 @@ public class SeeAlsoService extends AbstractResourceService {
@Autowired
ExternalLinksGenerator externalLinksGenerator;
public ExternalLinksGenerator getSeeAlso(Item item, List<Bundle> bundles) {
public ExternalLinksGenerator getSeeAlso(Item item) {
return externalLinksGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/manifest/seeAlso")
.setType(AnnotationGenerator.TYPE)
.setLabel(SEE_ALSO_LABEL);

View File

@@ -15,7 +15,6 @@ import org.apache.logging.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.ExternalLinksGenerator;
import org.dspace.app.rest.iiif.model.info.Info;
import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.Item;
@@ -34,6 +33,13 @@ public class SequenceService extends AbstractResourceService {
// TODO i18n
private static final String PDF_DOWNLOAD_LABEL = "Download as PDF";
/*
* The counter tracks the position of the bitstream in the list and is used to create the canvas identifier.
* The order of bitstreams (and thus page order in documents) is determined by position in the DSpace
* bundle.
*/
int counter = 0;
@Autowired
CanvasItemsGenerator sequenceGenerator;
@@ -48,12 +54,9 @@ public class SequenceService extends AbstractResourceService {
setConfiguration(configurationService);
}
public CanvasItemsGenerator getSequence(Item item, List<Bitstream> bitstreams, Context context, Info info) {
public CanvasItemsGenerator getSequence(Item item, Context context) {
sequenceGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/sequence/s0");
if (bitstreams.size() > 0) {
addCanvases(context, item, bitstreams, info);
}
addRendering(item, context);
return sequenceGenerator;
@@ -67,29 +70,15 @@ public class SequenceService extends AbstractResourceService {
* @param item the DSpace Item
* @param bitstreams list of DSpace bitstreams
*/
private void addCanvases(Context context, Item item,
List<Bitstream> bitstreams, Info info) {
/*
* The counter tracks the position of the bitstream in the list and is used to create the canvas identifier.
* The order of bitstreams (and thus page order in documents) is determined by position in the DSpace
* bundle.
*/
int counter = 0;
if (bitstreams == null || bitstreams.size() == 0) {
throw new RuntimeException("No bitstreams found for " + item.getID() +
". Cannot add media content to the manifest.");
}
for (Bitstream bitstream : bitstreams) {
UUID bitstreamId = bitstream.getID();
String mimeType = utils.getBitstreamMimeType(bitstream, context);
if (utils.checkImageMimeType(mimeType)) {
String manifestId = item.getID().toString();
CanvasGenerator canvasGenerator =
canvasService.getCanvas(manifestId, bitstreamId, mimeType, info, counter);
sequenceGenerator.addCanvas(canvasGenerator);
counter++;
}
}
public CanvasGenerator addCanvas(Context context, Item item, Bundle bnd, Bitstream bitstream) {
UUID bitstreamId = bitstream.getID();
String mimeType = utils.getBitstreamMimeType(bitstream, context);
String manifestId = item.getID().toString();
CanvasGenerator canvasGenerator =
canvasService.getCanvas(manifestId, bitstream, bnd, item, counter, mimeType);
String canvasIdentifier = sequenceGenerator.addCanvas(canvasGenerator);
counter++;
return canvasGenerator;
}
/**

View File

@@ -7,29 +7,29 @@
*/
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 java.util.stream.Collectors;
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.commons.lang3.StringUtils;
import org.apache.logging.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.Range;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Bitstream;
import org.dspace.content.BitstreamFormat;
import org.dspace.content.Bundle;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.MetadataValue;
import org.dspace.content.service.BitstreamService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.license.CreativeCommonsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -41,6 +41,18 @@ public class IIIFUtils {
// The canvas position will be appended to this string.
private static final String CANVAS_PATH_BASE = "/canvas/c";
public static final String METADATA_IIIF_ENABLED = "dspace.iiif.enabled";
public static final String METADATA_IIIF_SEARCH_ENABLED = "iiif.search.enabled";
public static final String METADATA_IIIF_LABEL = "iiif.label";
public static final String METADATA_IIIF_DESCRIPTION = "iiif.description";
public static final String METADATA_IIIF_TOC = "iiif.toc";
public static final String METADATA_IIIF_VIEWING_HINT = "iiif.viewing.hint";
public static final String METADATA_IMAGE_WIDTH = "iiif.image.width";
public static final String METADATA_IMAGE_HEIGTH = "iiif.image.heigth";
public static final String TOC_SEPARATOR = "|||";
public static final String TOC_SEPARATOR_REGEX = "\\|\\|\\|";
// get module subclass.
protected SimpleModule iiifModule = ObjectMapperFactory.getIiifModule();
// Use the object mapper subclass.
@@ -50,48 +62,55 @@ public class IIIFUtils {
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.
* This method returns the bundles holding IIIF resources if any.
* If there is no IIIF content available an empty bundle list is returned.
* @param item the DSpace item
* @param iiifBundle the name of the IIIF bundle
* @return DSpace bundle
*
* @return list of DSpace bundles with IIIF content
*/
public List<Bundle> getIiifBundle(Item item, String iiifBundle) {
boolean iiif = item.getMetadata().stream()
.filter(m -> m.getMetadataField().toString().contentEquals("dspace_entity_type"))
.anyMatch(m -> m.getValue().contentEquals("IIIF") ||
m.getValue().contentEquals("IIIFSearchable"));
public List<Bundle> getIiifBundles(Item item) {
boolean iiif = isIIIFEnabled(item);
List<Bundle> bundles = new ArrayList<>();
if (iiif) {
bundles = item.getBundles(iiifBundle);
if (bundles.size() == 0) {
bundles = item.getBundles("ORIGINAL");
}
bundles = item.getBundles().stream().filter(b -> isIIIFBundle(b)).collect(Collectors.toList());
}
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);
public boolean isIIIFEnabled(Item item) {
return item.getMetadata().stream()
.filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED))
.anyMatch(m -> m.getValue().equalsIgnoreCase("true") ||
m.getValue().equalsIgnoreCase("yes"));
}
/**
* 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) {
return bundles.get(0).getBitstreams();
private boolean isIIIFBundle(Bundle b) {
return !StringUtils.equalsAnyIgnoreCase(b.getName(), Constants.LICENSE_BUNDLE_NAME,
Constants.METADATA_BUNDLE_NAME, CreativeCommonsServiceImpl.CC_BUNDLE_NAME, "THUMBNAIL",
"BRANDED_PREVIEW", "TEXT")
&& b.getMetadata().stream()
.filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED))
.noneMatch(m -> m.getValue().equalsIgnoreCase("false") || m.getValue().equalsIgnoreCase("no"));
}
public List<Bitstream> getIiifBitstreams(Context context, Item item) {
List<Bitstream> bitstreams = new ArrayList<Bitstream>();
for (Bundle bnd : getIiifBundles(item)) {
bitstreams
.addAll(getIiifBitstreams(context, bnd));
}
return null;
return bitstreams;
}
public List<Bitstream> getIiifBitstreams(Context context, Bundle bundle) {
return bundle.getBitstreams().stream().filter(b -> isIiifBitstream(context, b))
.collect(Collectors.toList());
}
private boolean isIiifBitstream(Context context, Bitstream b) {
return checkImageMimeType(getBitstreamMimeType(b, context)) && b.getMetadata().stream()
.filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED))
.noneMatch(m -> m.getValue().equalsIgnoreCase("false") || m.getValue().equalsIgnoreCase("no"));
}
/**
@@ -105,101 +124,39 @@ public class IIIFUtils {
BitstreamFormat bitstreamFormat = bitstream.getFormat(context);
return bitstreamFormat.getMIMEType();
} catch (SQLException e) {
e.printStackTrace();
log.error(e.getMessage(), e);
}
return null;
}
/**
* Checks to see if the item is searchable. Based on the entity type.
* Checks to see if the item is searchable. Based on the {@link #METADATA_IIIF_SEARCH_ENABLED} metadata.
* @param item DSpace item
* @return true if searchable
* @return true if the iiif search is enabled
*/
public boolean isSearchable(Item item) {
return item.getMetadata().stream()
.filter(m -> m.getMetadataField().toString().contentEquals("dspace_entity_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;
.filter(m -> m.getMetadataField().toString('.').contentEquals("iiif.search.enabled"))
.anyMatch(m -> m.getValue().equalsIgnoreCase("true") ||
m.getValue().equalsIgnoreCase("yes"));
}
/**
* Retrives a bitstream based on its position in the IIIF bundle.
* @param context DSpace Context
* @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();
public Bitstream getBitstreamForCanvas(Context context, Item item, int canvasPosition) {
List<Bitstream> bitstreams = getIiifBitstreams(context, item);
try {
return bitstreams.get(canvasPosition);
return bitstreams.size() > canvasPosition ? bitstreams.get(canvasPosition) : null;
} 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<Range> getRangesFromInfoObject(Info info) {
if (info != null) {
return info.getStructures();
}
return null;
}
/**
* Extracts canvas position from the URL input path.
* @param canvasId e.g. "c12"
@@ -219,72 +176,6 @@ public class IIIFUtils {
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
@@ -312,4 +203,73 @@ public class IIIFUtils {
}
return false;
}
public List<Bitstream> getSeeAlsoBitstreams(Item item) {
return new ArrayList<Bitstream>();
}
public String getIIIFLabel(DSpaceObject dso, String defaultLabel) {
return dso.getMetadata().stream()
.filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_LABEL))
.findFirst().map(m -> m.getValue()).orElse(defaultLabel);
}
public String getIIIFDescription(DSpaceObject dso, String defaultDescription) {
return dso.getMetadata().stream()
.filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_DESCRIPTION))
.findFirst().map(m -> m.getValue()).orElse(defaultDescription);
}
public List<String> getIIIFToCs(DSpaceObject dso, String prefix) {
List<String> tocs = dso.getMetadata().stream()
.filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_TOC))
.map(m -> StringUtils.isNotBlank(prefix) ? prefix + TOC_SEPARATOR + m.getValue() : m.getValue())
.collect(Collectors.toList());
if (tocs.size() == 0 && StringUtils.isNotBlank(prefix)) {
return List.of(prefix);
} else {
return tocs;
}
}
public String getIIIFFirstToC(DSpaceObject dso) {
return dso.getMetadata().stream()
.filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_TOC))
.findFirst().map(m -> m.getValue()).orElse(null);
}
public String getIIIFViewingHint(Item item, String defaultHint) {
return item.getMetadata().stream()
.filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_VIEWING_HINT))
.findFirst().map(m -> m.getValue()).orElse(defaultHint);
}
public int getCanvasWidth(Bitstream bitstream, Bundle bundle, Item item, int defaultWidth) {
return getSizeFromMetadata(bitstream, METADATA_IMAGE_WIDTH,
getSizeFromMetadata(bundle, METADATA_IMAGE_WIDTH,
getSizeFromMetadata(item, METADATA_IMAGE_WIDTH, defaultWidth)));
}
public int getCanvasHeight(Bitstream bitstream, Bundle bundle, Item item, int defaultHeight) {
return getSizeFromMetadata(bitstream, METADATA_IMAGE_HEIGTH,
getSizeFromMetadata(bundle, METADATA_IMAGE_HEIGTH,
getSizeFromMetadata(item, METADATA_IMAGE_HEIGTH, defaultHeight)));
}
private int getSizeFromMetadata(DSpaceObject dso, String metadata, int defaultValue) {
return dso.getMetadata().stream()
.filter(m -> m.getMetadataField().toString('.').contentEquals(metadata))
.findFirst().map(m -> castToInt(m, defaultValue)).orElse(defaultValue);
}
private int castToInt(MetadataValue m, int defaultWidth) {
try {
Integer.parseInt(m.getValue());
} catch (NumberFormatException e) {
log.error("Error parsing " + m.getMetadataField().toString('.') + " of " + m.getDSpaceObject().getID()
+ " the value " + m.getValue() + " is not an integer. Returning the default.");
}
return defaultWidth;
}
}

View File

@@ -13,6 +13,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.io.InputStream;
import java.util.UUID;
import org.apache.commons.codec.CharEncoding;
import org.apache.commons.io.IOUtils;
@@ -31,33 +32,8 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
public static final String IIIFBundle = "IIIF";
/**
* info.json set for individual canvases.
*/
String info = "{\"globalDefaults\":{\"activated\": false,\"label\": \"\",\"width\": 0,\"height\": 0}," +
"\"canvases\":[{\"label\": \"Custom Label\", \"width\": 3163, \"height\": 4220, \"pos\": 0}]" +
",\"structures\": []}";
/**
* info.json with structures and global canvas setting.
*/
String infoWithStructures = "{\"globalDefaults\":" +
"{\"activated\": true,\"label\": \"Global\",\"width\": 2000,\"height\": 3000}," +
"\"canvases\":[]," +
"\"structures\": " +
"[{\"label\":\"Section 1\",\"start\":1}," +
"{\"label\":\"Section 2\",\"start\":2}]" +
"}";
/**
* info.json defaulting to global canvas settings.
*/
String globalInfoConfig = "{\"globalDefaults\":{\"activated\": true,\"label\": \"Global\",\"width\": 2000," +
"\"height\": 3000}, \"canvases\":[{\"label\": \"Custom Label\", \"width\": 3163, " +
"\"height\": 4220, \"pos\": 0}],\"structures\": []}";
@Test
public void missingBundleTest() throws Exception {
public void disabledTest() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
@@ -68,13 +44,28 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withTitle("Public item 1")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.withEntityType("IIIFSearchable")
.build();
context.restoreAuthSystemState();
// Status 500
getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest"))
.andExpect(status().is(500));
Item publicItem2 = ItemBuilder.createItem(context, col1)
.withTitle("Public item 2")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.disableIIIF()
.build();
context.restoreAuthSystemState();
// Status 404
getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest"))
.andExpect(status().is(404));
getClient().perform(get("/iiif/" + publicItem2.getID() + "/manifest"))
.andExpect(status().is(404));
}
@Test
public void notFoundTest() throws Exception {
// Status 404
getClient().perform(get("/iiif/" + UUID.randomUUID().toString() + "/manifest"))
.andExpect(status().is(404));
}
@Test
@@ -89,7 +80,8 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withTitle("Public item 1")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.withEntityType("IIIFSearchable")
.enableIIIF()
.enableIIIFSearch()
.build();
String bitstreamContent = "ThisIsSomeDummyText";
@@ -97,8 +89,8 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
Bitstream bitstream2 = null;
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
bitstream1 = BitstreamBuilder
.createBitstream(context, publicItem1, is, IIIFBundle)
.withName("Bitstream1")
.createBitstream(context, publicItem1, is)
.withName("Bitstream1.jpg")
.withMimeType("image/jpeg")
.build();
}
@@ -106,8 +98,8 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
bitstream2 = BitstreamBuilder
.createBitstream(context, publicItem1, is, IIIFBundle)
.withName("Bitstream2")
.withMimeType("image/jpeg")
.withName("Bitstream2.png")
.withMimeType("image/png")
.build();
}
@@ -124,6 +116,13 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0")))
.andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Page 1")))
.andExpect(jsonPath("$.sequences[0].canvases[0].width", is(1200)))
.andExpect(jsonPath("$.sequences[0].canvases[0].images[0].resource.service.@id",
Matchers.endsWith(bitstream1.getID().toString())))
.andExpect(jsonPath("$.sequences[0].canvases[1].@id",
Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c1")))
.andExpect(jsonPath("$.sequences[0].canvases[1].label", is("Page 2")))
.andExpect(jsonPath("$.sequences[0].canvases[1].images[0].resource.service.@id",
Matchers.endsWith(bitstream2.getID().toString())))
.andExpect(jsonPath("$.related.@id",
Matchers.containsString("/items/" + publicItem1.getID())));
}
@@ -140,6 +139,9 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withTitle("Public item 1")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.enableIIIF()
.withIIIFCanvasWidth(2000)
.withIIIFCanvasHeight(3000)
.withEntityType("IIIFSearchable")
.build();
@@ -147,19 +149,14 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder
.createBitstream(context, publicItem1, is, IIIFBundle)
.withName("Bitstream1")
.withName("Bitstream1.jpg")
.withMimeType("image/jpeg")
.withIIIFLabel("Custom Label")
.withIIIFCanvasWidth(3163)
.withIIIFCanvasHeight(4220)
//.withMimeType("image/jpeg")
.build();
}
try (InputStream is = IOUtils.toInputStream(this.globalInfoConfig, CharEncoding.UTF_8)) {
BitstreamBuilder.
createBitstream(context, publicItem1, is, "IIIF")
.withName("info.json")
.withMimeType("application/json")
.build();
}
context.restoreAuthSystemState();
// Expect canvas label, width and height to match bitstream description.
getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest"))
@@ -185,23 +182,19 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withTitle("Public item 1")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.withEntityType("IIIFSearchable")
.enableIIIF()
.enableIIIFSearch()
.build();
String bitstreamContent = "ThisIsSomeText";
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder
.createBitstream(context, publicItem1, is, IIIFBundle)
.withName("Bitstream1")
.withMimeType("image/jpeg")
.build();
}
try (InputStream is = IOUtils.toInputStream(this.info, CharEncoding.UTF_8)) {
BitstreamBuilder.
createBitstream(context, publicItem1, is, "IIIF")
.withName("info.json")
.withMimeType("application/json")
.withName("Bitstream1.png")
.withMimeType("image/png")
.withIIIFLabel("Custom Label")
.withIIIFCanvasWidth(3163)
.withIIIFCanvasHeight(4220)
.build();
}
@@ -231,14 +224,14 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.withMetadata("dc", "rights", "uri", "https://license.org")
.withEntityType("IIIF")
.enableIIIF()
.build();
String bitstreamContent = "ThisIsSomeDummyText";
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder.
createBitstream(context, publicItem1, is, IIIFBundle)
.withName("Bitstream1")
.withName("Bitstream1.jpg")
.withMimeType("image/jpeg")
.build();
}
@@ -247,8 +240,8 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) {
BitstreamBuilder.
createBitstream(context, publicItem1, is, IIIFBundle)
.withName("Bitstream2")
.withMimeType("image/jpeg")
.withName("Bitstream2.png")
.withMimeType("image/png")
.build();
}
@@ -256,8 +249,8 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
try (InputStream is = IOUtils.toInputStream(bitstreamContent3, CharEncoding.UTF_8)) {
BitstreamBuilder.
createBitstream(context, publicItem1, is, IIIFBundle)
.withName("Bitstream3")
.withMimeType("image/jpeg")
.withName("Bitstream3.tiff")
.withMimeType("image/tiff")
.build();
}
@@ -292,37 +285,36 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withTitle("Public item 1")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.withEntityType("IIIFSearchable")
.withIIIFCanvasHeight(3000)
.withIIIFCanvasWidth(2000)
.withIIIFCanvasLabel("Global")
.enableIIIF()
.enableIIIFSearch()
.build();
String bitstreamContent = "ThisIsSomeText";
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder
.createBitstream(context, publicItem1, is, IIIFBundle)
.withName("Bitstream1")
.withName("Bitstream1.jpg")
.withMimeType("image/jpeg")
.withIIIFToC("Section 1")
.build();
}
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder
.createBitstream(context, publicItem1, is, IIIFBundle)
.withName("Bitstream2")
.withMimeType("image/jpeg")
.withName("Bitstream2.png")
.withMimeType("image/png")
.withIIIFToC("Section 2")
.build();
}
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder
.createBitstream(context, publicItem1, is, IIIFBundle)
.withName("Bitstream3")
.withMimeType("image/jpeg")
.build();
}
try (InputStream is = IOUtils.toInputStream(this.infoWithStructures, CharEncoding.UTF_8)) {
BitstreamBuilder.
createBitstream(context, publicItem1, is, "IIIF")
.withName("info.json")
.withMimeType("application/json")
.withName("Bitstream3.tiff")
.withMimeType("image/tiff")
.withIIIFToC("Section 2")
.build();
}
@@ -355,14 +347,14 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.withMetadata("dc", "rights", "uri", "https://license.org")
.withEntityType("IIIF")
.enableIIIF()
.build();
String bitstreamContent = "ThisIsSomeDummyText";
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder.
createBitstream(context, publicItem1, is, IIIFBundle)
.withName("Bitstream1")
createBitstream(context, publicItem1, is)
.withName("Bitstream1.jpg")
.withMimeType("image/jpeg")
.build();
}
@@ -393,14 +385,14 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.withMetadata("dc", "rights", "uri", "https://license.org")
.withEntityType("IIIF")
.enableIIIF()
.build();
String bitstreamContent = "ThisIsSomeDummyText";
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder.
createBitstream(context, publicItem1, is, IIIFBundle)
.withName("Bitstream1")
createBitstream(context, publicItem1, is)
.withName("Bitstream1.jpg")
.withMimeType("image/jpeg")
.build();
}
@@ -439,21 +431,21 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withTitle("Public item 1")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.withEntityType("IIIF")
.enableIIIF()
.build();
String bitstreamContent = "ThisIsSomeDummyText";
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder.
createBitstream(context, publicItem1, is)
.withName("Bitstream1")
.withName("Bitstream1.jpg")
.withMimeType("image/jpeg")
.build();
}
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder.
createBitstream(context, publicItem1, is)
.withName("Bitstream1")
.withName("Bitstream2.pdf")
.withMimeType("application/pdf")
.build();
}
@@ -484,14 +476,14 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withTitle("Public item 1")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.withEntityType("IIIF")
.enableIIIF()
.build();
String bitstreamContent = "ThisIsSomeDummyText";
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder.
createBitstream(context, publicItem1, is, IIIFBundle)
.withName("IMG1")
createBitstream(context, publicItem1, is)
.withName("IMG1.jpg")
.withMimeType("image/jpeg")
.build();
}
@@ -516,21 +508,21 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withTitle("Public item 1")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.withEntityType("IIIF")
.enableIIIF()
.build();
String bitstreamContent = "ThisIsSomeDummyText";
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder.
createBitstream(context, publicItem1, is, IIIFBundle)
.withName("IMG1")
createBitstream(context, publicItem1, is)
.withName("IMG1.jpg")
.withMimeType("image/jpeg")
.build();
}
context.restoreAuthSystemState();
// Status 500. The item contains only one bitstream. The item manifest likewise contains one canvas.
// Status 404. The item contains only one bitstream. The item manifest likewise contains one canvas.
getClient().perform(get("/iiif/" + publicItem1.getID() + "/canvas/c2"))
.andExpect(status().is(500));
.andExpect(status().is(404));
}
@Test
@@ -545,14 +537,14 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withTitle("Public item 1")
.withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John")
.withEntityType("IIIF")
.enableIIIF()
.build();
String bitstreamContent = "ThisIsSomeDummyText";
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder.
createBitstream(context, publicItem1, is, IIIFBundle)
.withName("IMG1")
createBitstream(context, publicItem1, is)
.withName("IMG1.jpg")
.withMimeType("image/jpeg")
.build();
}

View File

@@ -818,7 +818,7 @@ registry.metadata.load = schema-periodical-types.xml
registry.metadata.load = schema-publicationIssue-types.xml
registry.metadata.load = schema-publicationVolume-types.xml
registry.metadata.load = dspace-types.xml
registry.metadata.load = iiif-types.xml
#---------------------------------------------------------------#

View File

@@ -11,8 +11,51 @@ iiif.cors.allowed-origins = ${dspace.ui.url}
# (Requires reboot of servlet container, e.g. Tomcat, to reload)
iiif.cors.allow-credentials = false
# metadata to include at the resource level in the manifest
# labels are set in the Messages.properties i18n file
iiif.metadata.item = dc.title
iiif.metadata.item = dc.date.issued
iiif.metadata.item = dc.contributor.*
iiif.metadata.item = dc.description.abstract
# metadata to be added to the canvas from the bitstream, labels are set in
the Messages.properties i18n file. It is possible to include the technical
# information stored in the bitstream format registry and in the checksum using the placeholder
# @format@ to include the short description of the bitstream format as entered by the user or
# stored in the registry
# @mimemtype@ to include the mimetype associated with the bitstream format in the registry
# @checksum@ to include the computed checksum for the file and the used algorithm
# @bytes@ to include the size of the images in bytes
iiif.metadata.bitstream = dc.title
iiif.metadata.bitstream = dc.description
iiif.metadata.bitstream = @format@
iiif.metadata.bitstream = @mimetype@
iiif.metadata.bitstream = iiif.image.width
iiif.metadata.bitstream = iiif.image.height
iiif.metadata.bitstream = @bytes@
iiif.metadata.bitstream = @checksum@
# the metadata to use to provide machine readable information about the resource right usage
iiif.license.uri = dc.rights.uri
# static text to be used as attribution in the iiif manifests
iiif.attribution = ${dspace.name}
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
# the iiif.view.hint metadata
iiif.document.viewing.hint =
# ????
iiif.url =
iiif.image.server =
iiif.solr.search.url =
# ????
iiif.bitstream.url =
# default value for the canvas size. Can be overridden at the item, bundle or bitstream level
# via the iiif.image.width e iiif.image.height metadata
# iiif.canvas.default-width = 1200
# iiif.canvas.default-heigth = 1600

View File

@@ -37,4 +37,10 @@
<scope_note>Stores the type of Entity that a specific Item represents</scope_note>
</dc-type>
<dc-type>
<schema>dspace</schema>
<element>iiif</element>
<qualifier>enabled</qualifier>
<scope_note>Stores a boolean text value (true or false) to indicate if the iiif feature is enabled or not for the dspace object. If absent the value is derived from the parent dspace object</scope_note>
</dc-type>
</dspace-dc-types>

View File

@@ -0,0 +1,66 @@
<dspace-dc-types>
<dspace-header>
<title>DSpace IIIF Schema</title>
<contributor.author>Andrea Bollini</contributor.author>
</dspace-header>
<dc-schema>
<name>iiif</name>
<namespace>http://dspace.org/iiif</namespace>
</dc-schema>
<dc-type>
<schema>iiif</schema>
<element>label</element>
<scope_note>Metadata field used to set the IIIF label associated with the resource otherwise the system will derive one according to the configuration and metadata</scope_note>
</dc-type>
<dc-type>
<schema>iiif</schema>
<element>description</element>
<scope_note>Metadata field used to set the IIIF description associated with the resource</scope_note>
</dc-type>
<dc-type>
<schema>iiif</schema>
<element>toc</element>
<scope_note>Metadata field used to set the position of the iiif resource in the structure. Levels are separated by triple pipe ||| can be applied to Bundles and Bitstreams</scope_note>
</dc-type>
<dc-type>
<schema>iiif</schema>
<element>canvas</element>
<qualifier>label</qualifier>
<scope_note>Metadata field used to set the base label used to name all the canvas in the Item. The canvas label will be generated using the value of this metadata as prefix and the canvas position</scope_note>
</dc-type>
<dc-type>
<schema>iiif</schema>
<element>viewing</element>
<qualifier>hint</qualifier>
<scope_note>Metadata field used to set the viewing hint overriding the configuration value if any</scope_note>
</dc-type>
<dc-type>
<schema>iiif</schema>
<element>image</element>
<qualifier>width</qualifier>
<scope_note>Metadata field used to store the width of an image in px</scope_note>
</dc-type>
<dc-type>
<schema>iiif</schema>
<element>image</element>
<qualifier>height</qualifier>
<scope_note>Metadata field used to store the height of an image in px</scope_note>
</dc-type>
<dc-type>
<schema>iiif</schema>
<element>search</element>
<qualifier>enabled</qualifier>
<scope_note>Metadata field used to enable the IIIF Search service at the item level</scope_note>
</dc-type>
</dspace-dc-types>