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; 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 { private Bundle getOriginalBundle(Item item) throws SQLException, AuthorizeException {
List<Bundle> bundles = itemService.getBundles(item, ORIGINAL); List<Bundle> bundles = itemService.getBundles(item, ORIGINAL);
Bundle targetBundle = null; Bundle targetBundle = null;
@@ -199,4 +220,5 @@ public class BitstreamBuilder extends AbstractDSpaceObjectBuilder<Bitstream> {
protected DSpaceObjectService<Bitstream> getService() { protected DSpaceObjectService<Bitstream> getService() {
return bitstreamService; return bitstreamService;
} }
} }

View File

@@ -115,6 +115,30 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder<Item> {
return addMetadataValue(item, MetadataSchemaEnum.DC.getName(), "description", "provenance", provenanceData); 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, public ItemBuilder withMetadata(final String schema, final String element, final String qualifier,
final String value) { final String value) {
return addMetadataValue(item, schema, element, qualifier, value); return addMetadataValue(item, schema, element, qualifier, value);
@@ -220,4 +244,5 @@ public class ItemBuilder extends AbstractDSpaceObjectBuilder<Item> {
c.complete(); 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.CanvasLookupService;
import org.dspace.app.rest.iiif.service.ManifestService; import org.dspace.app.rest.iiif.service.ManifestService;
import org.dspace.app.rest.iiif.service.SearchService; 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.Item;
import org.dspace.content.service.BitstreamService; import org.dspace.content.service.BitstreamService;
import org.dspace.content.service.ItemService; import org.dspace.content.service.ItemService;
@@ -48,6 +49,8 @@ public class IIIFServiceFacade {
@Autowired @Autowired
CanvasLookupService canvasLookupService; CanvasLookupService canvasLookupService;
@Autowired
IIIFUtils utils;
/** /**
* The manifest response contains sufficient information for the client to initialize itself * The manifest response contains sufficient information for the client to initialize itself
@@ -71,7 +74,7 @@ public class IIIFServiceFacade {
} catch (SQLException e) { } catch (SQLException e) {
throw new RuntimeException(e.getMessage(), 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"); throw new ResourceNotFoundException("IIIF manifest for id " + id + " not found");
} }
return manifestService.getManifest(item, context); return manifestService.getManifest(item, context);

View File

@@ -34,6 +34,10 @@ public class CanvasGenerator implements IIIFResource {
return this; return this;
} }
public String getIdentifier() {
return identifier;
}
/** /**
* Every canvas must have a label to display. * Every canvas must have a label to display.
* @param label * @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. * Add a Canvas to the sequence.
* @param canvas wrapper for Canvas * @param canvas wrapper for Canvas
*/ */
public void addCanvas(org.dspace.app.rest.iiif.model.generator.CanvasGenerator canvas) { public String addCanvas(org.dspace.app.rest.iiif.model.generator.CanvasGenerator canvas) {
this.canvas.add((Canvas) canvas.getResource()); Canvas resource = (Canvas) canvas.getResource();
this.canvas.add(resource);
return resource.getIdentifier().toString();
} }
@Override @Override

View File

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

View File

@@ -8,6 +8,7 @@
package org.dspace.app.rest.iiif.model.generator; package org.dspace.app.rest.iiif.model.generator;
import de.digitalcollections.iiif.model.MetadataEntry; import de.digitalcollections.iiif.model.MetadataEntry;
import de.digitalcollections.iiif.model.PropertyValue;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
@@ -15,6 +16,7 @@ public class MetadataEntryGenerator implements IIIFValue {
private String field; private String field;
private String value; private String value;
private String[] rest;
/** /**
* Set metadata field name. * Set metadata field name.
@@ -28,12 +30,19 @@ public class MetadataEntryGenerator implements IIIFValue {
* Set metadata value. * Set metadata value.
* @param value metadata value * @param value metadata value
*/ */
public void setValue(String value) { public void setValue(String value, String... rest) {
this.value = value; this.value = value;
this.rest = rest;
} }
@Override @Override
public MetadataEntry getValue() { 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, * 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. * 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" * This is used to populate the "structures" element of the Manifest. The structure is derived from the iiif.toc
* file for ranges.) * metadata and the ordered sequence of bitstreams (canvases)
*/ */
public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFResource { public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator.IIIFResource {
private String identifier; private String identifier;
private String label; private String label;
private final List<Canvas> canvasList = new ArrayList<>(); private final List<Canvas> canvasList = new ArrayList<>();
private final List<RangeGenerator> rangesList = new ArrayList<>();
/** /**
* Sets mandatory range identifier. * Sets mandatory range identifier.
@@ -38,6 +39,10 @@ public class RangeGenerator implements org.dspace.app.rest.iiif.model.generator.
return this; return this;
} }
public String getIdentifier() {
return identifier;
}
/** /**
* Sets mandatory range label. * Sets mandatory range label.
* @param label 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) { for (Canvas canvas : canvasList) {
range.addCanvas(canvas); range.addCanvas(canvas);
} }
for (RangeGenerator rg : rangesList) {
range.addRange((Range) rg.getResource());
}
return range; 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 CLIENT_URL;
protected String IIIF_LOGO_IMAGE; protected String IIIF_LOGO_IMAGE;
protected String BITSTREAM_PATH_PREFIX; protected String BITSTREAM_PATH_PREFIX;
protected int DEFAULT_CANVAS_WIDTH;
protected int DEFAULT_CANVAS_HEIGHT;
/** /**
* Possible values: "paged" or "individuals". The property * Possible values: "paged" or "individuals". The property
* value is set in dspace configuration. * value is set in dspace configuration.
*/ */
protected static String DOCUMENT_VIEWING_HINT; 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. // Paths for IIIF Image API requests.
protected static final String THUMBNAIL_PATH = "/full/90,/0/default.jpg"; protected static final String THUMBNAIL_PATH = "/full/90,/0/default.jpg";
protected static final String IMAGE_PATH = "/full/full/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 @Autowired
IIIFUtils utils; IIIFUtils utils;
@@ -65,6 +65,10 @@ public abstract class AbstractResourceService {
DOCUMENT_VIEWING_HINT = configurationService.getProperty("iiif.document.viewing.hint"); DOCUMENT_VIEWING_HINT = configurationService.getProperty("iiif.document.viewing.hint");
CLIENT_URL = configurationService.getProperty("dspace.ui.url"); CLIENT_URL = configurationService.getProperty("dspace.ui.url");
IIIF_LOGO_IMAGE = configurationService.getProperty("iiif.logo.image"); 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.app.rest.iiif.service.util.IIIFUtils;
import org.dspace.content.Bitstream; import org.dspace.content.Bitstream;
import org.dspace.content.BitstreamFormat; import org.dspace.content.BitstreamFormat;
import org.dspace.content.Bundle;
import org.dspace.content.Item; import org.dspace.content.Item;
import org.dspace.content.service.BitstreamFormatService; import org.dspace.content.service.BitstreamFormatService;
import org.dspace.content.service.BitstreamService; import org.dspace.content.service.BitstreamService;
@@ -84,28 +83,23 @@ public class AnnotationListService extends AbstractResourceService {
// AnnotationList requires an identifier. // AnnotationList requires an identifier.
annotationList.setIdentifier(IIIF_ENDPOINT + id + "/manifest/seeAlso"); annotationList.setIdentifier(IIIF_ENDPOINT + id + "/manifest/seeAlso");
// Get the "OtherContent" bundle for the item. Add // Get the "seeAlso" bitstreams for the item. Add
// Annotations for each bitstream found in the bundle. // Annotations for each bitstream found.
List<Bundle> bundles = utils.getBundle(item, OTHER_CONTENT_BUNDLE); List<Bitstream> bitstreams = utils.getSeeAlsoBitstreams(item);
if (bundles.size() > 0) { for (Bitstream bitstream : bitstreams) {
for (Bundle bundle : bundles) { BitstreamFormat format;
List<Bitstream> bitstreams = bundle.getBitstreams(); String mimetype;
for (Bitstream bitstream : bitstreams) { try {
BitstreamFormat format; format = bitstream.getFormat(context);
String mimetype; mimetype = format.getMIMEType();
try { } catch (SQLException e) {
format = bitstream.getFormat(context); throw new RuntimeException(e.getMessage(), e);
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);
}
} }
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()); return utils.asJson(annotationList.getResource());
} }

View File

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

View File

@@ -12,7 +12,10 @@ import java.util.UUID;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.dspace.app.rest.iiif.model.generator.CanvasGenerator; 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.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.dspace.services.ConfigurationService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope; 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); 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 @Autowired
ImageContentService imageContentService; ImageContentService imageContentService;
@Autowired
IIIFUtils utils;
/** /**
* Constructor. * Constructor.
*
* @param configurationService the DSpace configuration service. * @param configurationService the DSpace configuration service.
*/ */
public CanvasService(ConfigurationService configurationService) { 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 * Creates a single Canvas object. If canvas parameters are provided by the Info
* Info object they are used. If canvas parameters are unavailable, default values * object they are used. If canvas parameters are unavailable, default values
* are used instead. * 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 bitstreamId uuid of the bitstream
* @param mimeType the mimetype of the bitstream * @param mimeType the mimetype of the bitstream
* @param info parameters for this canvas * @param info parameters for this canvas
* @param count the canvas position in the sequence. * @param count the canvas position in the sequence.
* @return canvas object * @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; int pagePosition = count + 1;
// Defaults settings. Used if no info.json is provided. String label = utils.getIIIFLabel(bitstream, "Page " + pagePosition);
String label = "Page " + pagePosition; int canvasWidth = utils.getCanvasWidth(bitstream, bundle, item, DEFAULT_CANVAS_WIDTH);
int canvasWidth = DEFAULT_CANVAS_WIDTH; int canvasHeight = utils.getCanvasHeight(bitstream, bundle, item, DEFAULT_CANVAS_HEIGHT);
int canvasHeight = DEFAULT_CANVAS_HEIGHT; UUID bitstreamId = bitstream.getID();
// Override with settings from info.json, if available. ImageContentGenerator image = imageContentService.getImageContent(bitstreamId, mimeType,
if (info != null && info.getGlobalDefaults() != null && info.getCanvases() != null) { imageUtil.getImageProfile(), IMAGE_PATH);
// 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 ImageContentGenerator thumb = imageContentService.getImageContent(bitstreamId, mimeType,
.getImageContent(bitstreamId, mimeType, imageUtil.getImageProfile(), IMAGE_PATH); thumbUtil.getThumbnailProfile(), THUMBNAIL_PATH);
ImageContentGenerator thumb = imageContentService
.getImageContent(bitstreamId, mimeType, thumbUtil.getThumbnailProfile(), THUMBNAIL_PATH);
return new CanvasGenerator().setIdentifier(IIIF_ENDPOINT + manifestId + "/canvas/c" + count) return new CanvasGenerator().setIdentifier(IIIF_ENDPOINT + manifestId + "/canvas/c" + count)
.addImage(image.getResource()) .addImage(image.getResource()).addThumbnail(thumb.getResource()).setHeight(canvasHeight)
.addThumbnail(thumb.getResource()) .setWidth(canvasWidth).setLabel(label);
.setHeight(canvasHeight)
.setWidth(canvasWidth)
.setLabel(label);
} }
/** /**
* Ranges expect the Canvas object to have only an identifier. * 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 * @param startCanvas the position of the canvas in list
* @return * @return
*/ */

View File

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

View File

@@ -7,16 +7,21 @@
*/ */
package org.dspace.app.rest.iiif.service; 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.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger; 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.ContentSearchGenerator;
import org.dspace.app.rest.iiif.model.generator.ImageContentGenerator; 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.generator.ManifestGenerator;
import org.dspace.app.rest.iiif.model.info.Info; import org.dspace.app.rest.iiif.model.generator.RangeGenerator;
import org.dspace.app.rest.iiif.model.info.Range;
import org.dspace.app.rest.iiif.service.util.IIIFUtils; 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.Bitstream;
import org.dspace.content.Bundle; import org.dspace.content.Bundle;
import org.dspace.content.Item; import org.dspace.content.Item;
@@ -67,13 +72,17 @@ public class ManifestService extends AbstractResourceService {
@Autowired @Autowired
ManifestGenerator manifestGenerator; ManifestGenerator manifestGenerator;
@Autowired
MetadataExposureService metadataExposureService;
protected String[] METADATA_FIELDS;
/** /**
* Constructor. * Constructor.
* @param configurationService the DSpace configuration service. * @param configurationService the DSpace configuration service.
*/ */
public ManifestService(ConfigurationService configurationService) { public ManifestService(ConfigurationService configurationService) {
setConfiguration(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) { private void populateManifest(Item item, Context context) {
// If an IIIF bundle is found it will be used. Otherwise, // If an IIIF bundle is found it will be used. Otherwise,
// images in the ORIGINAL bundle will be used. // images in the ORIGINAL bundle will be used.
List<Bundle> bundles = utils.getIiifBundle(item, IIIF_BUNDLE); List<Bundle> bundles = utils.getIiifBundles(item);
List<Bitstream> bitstreams = utils.getBitstreams(bundles); String manifestId = getManifestId(item.getID());
Info info = utils.validateInfoForManifest(utils.getInfo(context, item, IIIF_BUNDLE), bitstreams); manifestGenerator.setIdentifier(manifestId);
manifestGenerator.setIdentifier(getManifestId(item.getID()));
manifestGenerator.setLabel(item.getName()); manifestGenerator.setLabel(item.getName());
setLogoContainer(); setLogoContainer();
addRelated(item); addRelated(item);
addSearchService(item); addSearchService(item);
addMetadata(item.getMetadata()); addMetadata(context, item);
addViewingHint(bitstreams.size()); addViewingHint(item);
addThumbnail(bundles, context); addThumbnail(item, context);
addSequence(item, bitstreams, context, info);
addRanges(info, item.getID().toString()); // 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); 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. * 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) { private void addMetadata(Context context, Item item) {
for (MetadataValue meta : metadata) { for (String field : METADATA_FIELDS) {
String field = utils.getMetadataFieldName(meta); String[] eq = field.split("\\.");
if (field.contentEquals("rights.uri")) { String schema = eq[0];
manifestGenerator.addMetadata(field, meta.getValue()); String element = eq[1];
manifestGenerator.addLicense(meta.getValue()); String qualifier = null;
} else if (field.contentEquals("description")) { if (eq.length > 2) {
// Add manifest description field. qualifier = eq[2];
manifestGenerator.addDescription(field, meta.getValue()); }
} else { List<MetadataValue> metadata = item.getItemService().getMetadata(item, schema, element, qualifier,
// Exclude DSpace description.provenance field. Item.ANY);
if (!field.contentEquals("description.provenance")) { List<String> values = new ArrayList<String>();
// Everything else, add to manifest metadata fields. for (MetadataValue meta : metadata) {
manifestGenerator.addMetadata(field, meta.getValue()); // 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. * 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) { private void addViewingHint(Item item) {
if (bitstreamCount > 2) { manifestGenerator.addViewingHint(utils.getIIIFViewingHint(item, DOCUMENT_VIEWING_HINT));
manifestGenerator.addViewingHint(DOCUMENT_VIEWING_HINT);
}
} }
/** /**
@@ -192,10 +265,7 @@ public class ManifestService extends AbstractResourceService {
* @param item the DSpace Item. * @param item the DSpace Item.
*/ */
private void addSeeAlso(Item item) { private void addSeeAlso(Item item) {
List<Bundle> bundles = utils.getBundle(item, OTHER_CONTENT_BUNDLE); manifestGenerator.addSeeAlso(seeAlsoService.getSeeAlso(item));
if (bundles.size() > 0) {
manifestGenerator.addSeeAlso(seeAlsoService.getSeeAlso(item, bundles));
}
} }
/** /**
@@ -215,33 +285,18 @@ public class ManifestService extends AbstractResourceService {
} }
/** /**
* Adds Ranges to manifest structures element. * Adds thumbnail to the manifest. Uses first image in the manifest.
* Ranges are defined in the info.json file. * @param item the DSpace Item
* @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
* @param context DSpace context * @param context DSpace context
*/ */
private void addThumbnail(List<Bundle> bundles, Context context) { private void addThumbnail(Item item, Context context) {
List<Bitstream> bitstreams = utils.getBitstreams(bundles); List<Bitstream> bitstreams = utils.getIiifBitstreams(context, item);
if (bitstreams != null && bitstreams.size() > 0) { if (bitstreams != null && bitstreams.size() > 0) {
String mimeType = utils.getBitstreamMimeType(bitstreams.get(0), context); String mimeType = utils.getBitstreamMimeType(bitstreams.get(0), context);
if (utils.checkImageMimeType(mimeType)) { ImageContentGenerator image = imageContentService
ImageContentGenerator image = imageContentService .getImageContent(bitstreams.get(0).getID(), mimeType,
.getImageContent(bitstreams.get(0).getID(), mimeType, thumbUtil.getThumbnailProfile(), THUMBNAIL_PATH);
thumbUtil.getThumbnailProfile(), THUMBNAIL_PATH); manifestGenerator.addThumbnail(image);
manifestGenerator.addThumbnail(image);
}
} }
} }

View File

@@ -7,12 +7,7 @@
*/ */
package org.dspace.app.rest.iiif.service; 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.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.dspace.services.ConfigurationService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -30,36 +25,12 @@ public class RangeService extends AbstractResourceService {
} }
/** /**
* Adds Ranges to manifest structures element. * Ranges expect the Sub range object to have only an identifier.
* Ranges are defined in the info.json file. *
* @param info * @param range the sub range to reference
* @param identifier * @return RangeGenerator able to create the reference
*/ */
public List<RangeGenerator> getRanges(Info info, String identifier) { public RangeGenerator getRangeReference(RangeGenerator range) {
List<RangeGenerator> ranges = new ArrayList<>(); return new RangeGenerator().setIdentifier(range.getIdentifier());
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;
} }
} }

View File

@@ -7,11 +7,8 @@
*/ */
package org.dspace.app.rest.iiif.service; 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.AnnotationGenerator;
import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator; import org.dspace.app.rest.iiif.model.generator.ExternalLinksGenerator;
import org.dspace.content.Bundle;
import org.dspace.content.Item; import org.dspace.content.Item;
import org.dspace.services.ConfigurationService; import org.dspace.services.ConfigurationService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -31,7 +28,7 @@ public class SeeAlsoService extends AbstractResourceService {
@Autowired @Autowired
ExternalLinksGenerator externalLinksGenerator; ExternalLinksGenerator externalLinksGenerator;
public ExternalLinksGenerator getSeeAlso(Item item, List<Bundle> bundles) { public ExternalLinksGenerator getSeeAlso(Item item) {
return externalLinksGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/manifest/seeAlso") return externalLinksGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/manifest/seeAlso")
.setType(AnnotationGenerator.TYPE) .setType(AnnotationGenerator.TYPE)
.setLabel(SEE_ALSO_LABEL); .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.CanvasGenerator;
import org.dspace.app.rest.iiif.model.generator.CanvasItemsGenerator; 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.generator.ExternalLinksGenerator;
import org.dspace.app.rest.iiif.model.info.Info;
import org.dspace.content.Bitstream; import org.dspace.content.Bitstream;
import org.dspace.content.Bundle; import org.dspace.content.Bundle;
import org.dspace.content.Item; import org.dspace.content.Item;
@@ -34,6 +33,13 @@ public class SequenceService extends AbstractResourceService {
// TODO i18n // TODO i18n
private static final String PDF_DOWNLOAD_LABEL = "Download as PDF"; 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 @Autowired
CanvasItemsGenerator sequenceGenerator; CanvasItemsGenerator sequenceGenerator;
@@ -48,12 +54,9 @@ public class SequenceService extends AbstractResourceService {
setConfiguration(configurationService); 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"); sequenceGenerator.setIdentifier(IIIF_ENDPOINT + item.getID() + "/sequence/s0");
if (bitstreams.size() > 0) {
addCanvases(context, item, bitstreams, info);
}
addRendering(item, context); addRendering(item, context);
return sequenceGenerator; return sequenceGenerator;
@@ -67,29 +70,15 @@ public class SequenceService extends AbstractResourceService {
* @param item the DSpace Item * @param item the DSpace Item
* @param bitstreams list of DSpace bitstreams * @param bitstreams list of DSpace bitstreams
*/ */
private void addCanvases(Context context, Item item, public CanvasGenerator addCanvas(Context context, Item item, Bundle bnd, Bitstream bitstream) {
List<Bitstream> bitstreams, Info info) { UUID bitstreamId = bitstream.getID();
/* String mimeType = utils.getBitstreamMimeType(bitstream, context);
* The counter tracks the position of the bitstream in the list and is used to create the canvas identifier. String manifestId = item.getID().toString();
* The order of bitstreams (and thus page order in documents) is determined by position in the DSpace CanvasGenerator canvasGenerator =
* bundle. canvasService.getCanvas(manifestId, bitstream, bnd, item, counter, mimeType);
*/ String canvasIdentifier = sequenceGenerator.addCanvas(canvasGenerator);
int counter = 0; counter++;
if (bitstreams == null || bitstreams.size() == 0) { return canvasGenerator;
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++;
}
}
} }
/** /**

View File

@@ -7,29 +7,29 @@
*/ */
package org.dspace.app.rest.iiif.service.util; package org.dspace.app.rest.iiif.service.util;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.module.SimpleModule;
import de.digitalcollections.iiif.model.sharedcanvas.Resource; import de.digitalcollections.iiif.model.sharedcanvas.Resource;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.dspace.app.rest.iiif.model.ObjectMapperFactory; 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.Bitstream;
import org.dspace.content.BitstreamFormat; import org.dspace.content.BitstreamFormat;
import org.dspace.content.Bundle; import org.dspace.content.Bundle;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item; import org.dspace.content.Item;
import org.dspace.content.MetadataValue; import org.dspace.content.MetadataValue;
import org.dspace.content.service.BitstreamService; import org.dspace.content.service.BitstreamService;
import org.dspace.core.Constants;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.license.CreativeCommonsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -41,6 +41,18 @@ public class IIIFUtils {
// The canvas position will be appended to this string. // The canvas position will be appended to this string.
private static final String CANVAS_PATH_BASE = "/canvas/c"; 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. // get module subclass.
protected SimpleModule iiifModule = ObjectMapperFactory.getIiifModule(); protected SimpleModule iiifModule = ObjectMapperFactory.getIiifModule();
// Use the object mapper subclass. // Use the object mapper subclass.
@@ -50,48 +62,55 @@ public class IIIFUtils {
protected BitstreamService bitstreamService; protected BitstreamService bitstreamService;
/** /**
* For IIIF entities, this method returns the bundle assigned to IIIF * This method returns the bundles holding IIIF resources if any.
* bitstreams. If the item is not an IIIF entity, the default (ORIGINAL) * If there is no IIIF content available an empty bundle list is returned.
* bundle list is returned instead.
* @param item the DSpace item * @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) { public List<Bundle> getIiifBundles(Item item) {
boolean iiif = item.getMetadata().stream() boolean iiif = isIIIFEnabled(item);
.filter(m -> m.getMetadataField().toString().contentEquals("dspace_entity_type"))
.anyMatch(m -> m.getValue().contentEquals("IIIF") ||
m.getValue().contentEquals("IIIFSearchable"));
List<Bundle> bundles = new ArrayList<>(); List<Bundle> bundles = new ArrayList<>();
if (iiif) { if (iiif) {
bundles = item.getBundles(iiifBundle); bundles = item.getBundles().stream().filter(b -> isIIIFBundle(b)).collect(Collectors.toList());
if (bundles.size() == 0) {
bundles = item.getBundles("ORIGINAL");
}
} }
return bundles; return bundles;
} }
/** public boolean isIIIFEnabled(Item item) {
* Returns the requested bundle. return item.getMetadata().stream()
* @param item DSpace item .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED))
* @param name bundle name .anyMatch(m -> m.getValue().equalsIgnoreCase("true") ||
* @return m.getValue().equalsIgnoreCase("yes"));
*/
public List<Bundle> getBundle(Item item, String name) {
return item.getBundles(name);
} }
/** private boolean isIIIFBundle(Bundle b) {
* Returns bitstreams for the first bundle in the list. return !StringUtils.equalsAnyIgnoreCase(b.getName(), Constants.LICENSE_BUNDLE_NAME,
* @param bundles list of DSpace bundles Constants.METADATA_BUNDLE_NAME, CreativeCommonsServiceImpl.CC_BUNDLE_NAME, "THUMBNAIL",
* @return list of bitstreams "BRANDED_PREVIEW", "TEXT")
*/ && b.getMetadata().stream()
public List<Bitstream> getBitstreams(List<Bundle> bundles) { .filter(m -> m.getMetadataField().toString('.').contentEquals(METADATA_IIIF_ENABLED))
if (bundles != null && bundles.size() > 0) { .noneMatch(m -> m.getValue().equalsIgnoreCase("false") || m.getValue().equalsIgnoreCase("no"));
return bundles.get(0).getBitstreams(); }
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); BitstreamFormat bitstreamFormat = bitstream.getFormat(context);
return bitstreamFormat.getMIMEType(); return bitstreamFormat.getMIMEType();
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace(); log.error(e.getMessage(), e);
} }
return null; 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 * @param item DSpace item
* @return true if searchable * @return true if the iiif search is enabled
*/ */
public boolean isSearchable(Item item) { public boolean isSearchable(Item item) {
return item.getMetadata().stream() return item.getMetadata().stream()
.filter(m -> m.getMetadataField().toString().contentEquals("dspace_entity_type")) .filter(m -> m.getMetadataField().toString('.').contentEquals("iiif.search.enabled"))
.anyMatch(m -> m.getValue().contentEquals("IIIFSearchable")); .anyMatch(m -> m.getValue().equalsIgnoreCase("true") ||
} m.getValue().equalsIgnoreCase("yes"));
/**
* 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. * Retrives a bitstream based on its position in the IIIF bundle.
* @param context DSpace Context
* @param item DSpace Item * @param item DSpace Item
* @param canvasPosition bitstream position * @param canvasPosition bitstream position
* @return bitstream * @return bitstream
*/ */
public Bitstream getBitstreamForCanvas(Item item, String bundleName, int canvasPosition) { public Bitstream getBitstreamForCanvas(Context context, Item item, int canvasPosition) {
List<Bundle> bundles = item.getBundles(bundleName); List<Bitstream> bitstreams = getIiifBitstreams(context, item);
if (bundles.size() == 0) {
return null;
}
List<Bitstream> bitstreams = bundles.get(0).getBitstreams();
try { try {
return bitstreams.get(canvasPosition); return bitstreams.size() > canvasPosition ? bitstreams.get(canvasPosition) : null;
} catch (RuntimeException e) { } catch (RuntimeException e) {
throw new RuntimeException("The requested canvas is not available", 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. * Extracts canvas position from the URL input path.
* @param canvasId e.g. "c12" * @param canvasId e.g. "c12"
@@ -219,72 +176,6 @@ public class IIIFUtils {
return CANVAS_PATH_BASE + 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. * Serializes the json response.
* @param resource to be serialized * @param resource to be serialized
@@ -312,4 +203,73 @@ public class IIIFUtils {
} }
return false; 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 static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.io.InputStream; import java.io.InputStream;
import java.util.UUID;
import org.apache.commons.codec.CharEncoding; import org.apache.commons.codec.CharEncoding;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
@@ -31,33 +32,8 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
public static final String IIIFBundle = "IIIF"; 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 @Test
public void missingBundleTest() throws Exception { public void disabledTest() throws Exception {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context) parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community") .withName("Parent Community")
@@ -68,13 +44,28 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withTitle("Public item 1") .withTitle("Public item 1")
.withIssueDate("2017-10-17") .withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John") .withAuthor("Smith, Donald").withAuthor("Doe, John")
.withEntityType("IIIFSearchable")
.build(); .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 @Test
@@ -89,7 +80,8 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withTitle("Public item 1") .withTitle("Public item 1")
.withIssueDate("2017-10-17") .withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John") .withAuthor("Smith, Donald").withAuthor("Doe, John")
.withEntityType("IIIFSearchable") .enableIIIF()
.enableIIIFSearch()
.build(); .build();
String bitstreamContent = "ThisIsSomeDummyText"; String bitstreamContent = "ThisIsSomeDummyText";
@@ -97,8 +89,8 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
Bitstream bitstream2 = null; Bitstream bitstream2 = null;
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
bitstream1 = BitstreamBuilder bitstream1 = BitstreamBuilder
.createBitstream(context, publicItem1, is, IIIFBundle) .createBitstream(context, publicItem1, is)
.withName("Bitstream1") .withName("Bitstream1.jpg")
.withMimeType("image/jpeg") .withMimeType("image/jpeg")
.build(); .build();
} }
@@ -106,8 +98,8 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
bitstream2 = BitstreamBuilder bitstream2 = BitstreamBuilder
.createBitstream(context, publicItem1, is, IIIFBundle) .createBitstream(context, publicItem1, is, IIIFBundle)
.withName("Bitstream2") .withName("Bitstream2.png")
.withMimeType("image/jpeg") .withMimeType("image/png")
.build(); .build();
} }
@@ -124,6 +116,13 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0"))) Matchers.containsString("/iiif/" + publicItem1.getID() + "/canvas/c0")))
.andExpect(jsonPath("$.sequences[0].canvases[0].label", is("Page 1"))) .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].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", .andExpect(jsonPath("$.related.@id",
Matchers.containsString("/items/" + publicItem1.getID()))); Matchers.containsString("/items/" + publicItem1.getID())));
} }
@@ -140,6 +139,9 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withTitle("Public item 1") .withTitle("Public item 1")
.withIssueDate("2017-10-17") .withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John") .withAuthor("Smith, Donald").withAuthor("Doe, John")
.enableIIIF()
.withIIIFCanvasWidth(2000)
.withIIIFCanvasHeight(3000)
.withEntityType("IIIFSearchable") .withEntityType("IIIFSearchable")
.build(); .build();
@@ -147,19 +149,14 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder BitstreamBuilder
.createBitstream(context, publicItem1, is, IIIFBundle) .createBitstream(context, publicItem1, is, IIIFBundle)
.withName("Bitstream1") .withName("Bitstream1.jpg")
.withMimeType("image/jpeg") .withMimeType("image/jpeg")
.withIIIFLabel("Custom Label")
.withIIIFCanvasWidth(3163)
.withIIIFCanvasHeight(4220)
//.withMimeType("image/jpeg")
.build(); .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(); context.restoreAuthSystemState();
// Expect canvas label, width and height to match bitstream description. // Expect canvas label, width and height to match bitstream description.
getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest")) getClient().perform(get("/iiif/" + publicItem1.getID() + "/manifest"))
@@ -185,23 +182,19 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withTitle("Public item 1") .withTitle("Public item 1")
.withIssueDate("2017-10-17") .withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John") .withAuthor("Smith, Donald").withAuthor("Doe, John")
.withEntityType("IIIFSearchable") .enableIIIF()
.enableIIIFSearch()
.build(); .build();
String bitstreamContent = "ThisIsSomeText"; String bitstreamContent = "ThisIsSomeText";
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder BitstreamBuilder
.createBitstream(context, publicItem1, is, IIIFBundle) .createBitstream(context, publicItem1, is, IIIFBundle)
.withName("Bitstream1") .withName("Bitstream1.png")
.withMimeType("image/jpeg") .withMimeType("image/png")
.build(); .withIIIFLabel("Custom Label")
} .withIIIFCanvasWidth(3163)
.withIIIFCanvasHeight(4220)
try (InputStream is = IOUtils.toInputStream(this.info, CharEncoding.UTF_8)) {
BitstreamBuilder.
createBitstream(context, publicItem1, is, "IIIF")
.withName("info.json")
.withMimeType("application/json")
.build(); .build();
} }
@@ -231,14 +224,14 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withIssueDate("2017-10-17") .withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John") .withAuthor("Smith, Donald").withAuthor("Doe, John")
.withMetadata("dc", "rights", "uri", "https://license.org") .withMetadata("dc", "rights", "uri", "https://license.org")
.withEntityType("IIIF") .enableIIIF()
.build(); .build();
String bitstreamContent = "ThisIsSomeDummyText"; String bitstreamContent = "ThisIsSomeDummyText";
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder. BitstreamBuilder.
createBitstream(context, publicItem1, is, IIIFBundle) createBitstream(context, publicItem1, is, IIIFBundle)
.withName("Bitstream1") .withName("Bitstream1.jpg")
.withMimeType("image/jpeg") .withMimeType("image/jpeg")
.build(); .build();
} }
@@ -247,8 +240,8 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) { try (InputStream is = IOUtils.toInputStream(bitstreamContent2, CharEncoding.UTF_8)) {
BitstreamBuilder. BitstreamBuilder.
createBitstream(context, publicItem1, is, IIIFBundle) createBitstream(context, publicItem1, is, IIIFBundle)
.withName("Bitstream2") .withName("Bitstream2.png")
.withMimeType("image/jpeg") .withMimeType("image/png")
.build(); .build();
} }
@@ -256,8 +249,8 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
try (InputStream is = IOUtils.toInputStream(bitstreamContent3, CharEncoding.UTF_8)) { try (InputStream is = IOUtils.toInputStream(bitstreamContent3, CharEncoding.UTF_8)) {
BitstreamBuilder. BitstreamBuilder.
createBitstream(context, publicItem1, is, IIIFBundle) createBitstream(context, publicItem1, is, IIIFBundle)
.withName("Bitstream3") .withName("Bitstream3.tiff")
.withMimeType("image/jpeg") .withMimeType("image/tiff")
.build(); .build();
} }
@@ -292,37 +285,36 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withTitle("Public item 1") .withTitle("Public item 1")
.withIssueDate("2017-10-17") .withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John") .withAuthor("Smith, Donald").withAuthor("Doe, John")
.withEntityType("IIIFSearchable") .withIIIFCanvasHeight(3000)
.withIIIFCanvasWidth(2000)
.withIIIFCanvasLabel("Global")
.enableIIIF()
.enableIIIFSearch()
.build(); .build();
String bitstreamContent = "ThisIsSomeText"; String bitstreamContent = "ThisIsSomeText";
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder BitstreamBuilder
.createBitstream(context, publicItem1, is, IIIFBundle) .createBitstream(context, publicItem1, is, IIIFBundle)
.withName("Bitstream1") .withName("Bitstream1.jpg")
.withMimeType("image/jpeg") .withMimeType("image/jpeg")
.withIIIFToC("Section 1")
.build(); .build();
} }
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder BitstreamBuilder
.createBitstream(context, publicItem1, is, IIIFBundle) .createBitstream(context, publicItem1, is, IIIFBundle)
.withName("Bitstream2") .withName("Bitstream2.png")
.withMimeType("image/jpeg") .withMimeType("image/png")
.withIIIFToC("Section 2")
.build(); .build();
} }
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder BitstreamBuilder
.createBitstream(context, publicItem1, is, IIIFBundle) .createBitstream(context, publicItem1, is, IIIFBundle)
.withName("Bitstream3") .withName("Bitstream3.tiff")
.withMimeType("image/jpeg") .withMimeType("image/tiff")
.build(); .withIIIFToC("Section 2")
}
try (InputStream is = IOUtils.toInputStream(this.infoWithStructures, CharEncoding.UTF_8)) {
BitstreamBuilder.
createBitstream(context, publicItem1, is, "IIIF")
.withName("info.json")
.withMimeType("application/json")
.build(); .build();
} }
@@ -355,14 +347,14 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withIssueDate("2017-10-17") .withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John") .withAuthor("Smith, Donald").withAuthor("Doe, John")
.withMetadata("dc", "rights", "uri", "https://license.org") .withMetadata("dc", "rights", "uri", "https://license.org")
.withEntityType("IIIF") .enableIIIF()
.build(); .build();
String bitstreamContent = "ThisIsSomeDummyText"; String bitstreamContent = "ThisIsSomeDummyText";
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder. BitstreamBuilder.
createBitstream(context, publicItem1, is, IIIFBundle) createBitstream(context, publicItem1, is)
.withName("Bitstream1") .withName("Bitstream1.jpg")
.withMimeType("image/jpeg") .withMimeType("image/jpeg")
.build(); .build();
} }
@@ -393,14 +385,14 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withIssueDate("2017-10-17") .withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John") .withAuthor("Smith, Donald").withAuthor("Doe, John")
.withMetadata("dc", "rights", "uri", "https://license.org") .withMetadata("dc", "rights", "uri", "https://license.org")
.withEntityType("IIIF") .enableIIIF()
.build(); .build();
String bitstreamContent = "ThisIsSomeDummyText"; String bitstreamContent = "ThisIsSomeDummyText";
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder. BitstreamBuilder.
createBitstream(context, publicItem1, is, IIIFBundle) createBitstream(context, publicItem1, is)
.withName("Bitstream1") .withName("Bitstream1.jpg")
.withMimeType("image/jpeg") .withMimeType("image/jpeg")
.build(); .build();
} }
@@ -439,21 +431,21 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withTitle("Public item 1") .withTitle("Public item 1")
.withIssueDate("2017-10-17") .withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John") .withAuthor("Smith, Donald").withAuthor("Doe, John")
.withEntityType("IIIF") .enableIIIF()
.build(); .build();
String bitstreamContent = "ThisIsSomeDummyText"; String bitstreamContent = "ThisIsSomeDummyText";
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder. BitstreamBuilder.
createBitstream(context, publicItem1, is) createBitstream(context, publicItem1, is)
.withName("Bitstream1") .withName("Bitstream1.jpg")
.withMimeType("image/jpeg") .withMimeType("image/jpeg")
.build(); .build();
} }
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder. BitstreamBuilder.
createBitstream(context, publicItem1, is) createBitstream(context, publicItem1, is)
.withName("Bitstream1") .withName("Bitstream2.pdf")
.withMimeType("application/pdf") .withMimeType("application/pdf")
.build(); .build();
} }
@@ -484,14 +476,14 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withTitle("Public item 1") .withTitle("Public item 1")
.withIssueDate("2017-10-17") .withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John") .withAuthor("Smith, Donald").withAuthor("Doe, John")
.withEntityType("IIIF") .enableIIIF()
.build(); .build();
String bitstreamContent = "ThisIsSomeDummyText"; String bitstreamContent = "ThisIsSomeDummyText";
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder. BitstreamBuilder.
createBitstream(context, publicItem1, is, IIIFBundle) createBitstream(context, publicItem1, is)
.withName("IMG1") .withName("IMG1.jpg")
.withMimeType("image/jpeg") .withMimeType("image/jpeg")
.build(); .build();
} }
@@ -516,21 +508,21 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withTitle("Public item 1") .withTitle("Public item 1")
.withIssueDate("2017-10-17") .withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John") .withAuthor("Smith, Donald").withAuthor("Doe, John")
.withEntityType("IIIF") .enableIIIF()
.build(); .build();
String bitstreamContent = "ThisIsSomeDummyText"; String bitstreamContent = "ThisIsSomeDummyText";
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder. BitstreamBuilder.
createBitstream(context, publicItem1, is, IIIFBundle) createBitstream(context, publicItem1, is)
.withName("IMG1") .withName("IMG1.jpg")
.withMimeType("image/jpeg") .withMimeType("image/jpeg")
.build(); .build();
} }
context.restoreAuthSystemState(); 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")) getClient().perform(get("/iiif/" + publicItem1.getID() + "/canvas/c2"))
.andExpect(status().is(500)); .andExpect(status().is(404));
} }
@Test @Test
@@ -545,14 +537,14 @@ public class IIIFControllerIT extends AbstractControllerIntegrationTest {
.withTitle("Public item 1") .withTitle("Public item 1")
.withIssueDate("2017-10-17") .withIssueDate("2017-10-17")
.withAuthor("Smith, Donald").withAuthor("Doe, John") .withAuthor("Smith, Donald").withAuthor("Doe, John")
.withEntityType("IIIF") .enableIIIF()
.build(); .build();
String bitstreamContent = "ThisIsSomeDummyText"; String bitstreamContent = "ThisIsSomeDummyText";
try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) {
BitstreamBuilder. BitstreamBuilder.
createBitstream(context, publicItem1, is, IIIFBundle) createBitstream(context, publicItem1, is)
.withName("IMG1") .withName("IMG1.jpg")
.withMimeType("image/jpeg") .withMimeType("image/jpeg")
.build(); .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-publicationIssue-types.xml
registry.metadata.load = schema-publicationVolume-types.xml registry.metadata.load = schema-publicationVolume-types.xml
registry.metadata.load = dspace-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) # (Requires reboot of servlet container, e.g. Tomcat, to reload)
iiif.cors.allow-credentials = false 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> <scope_note>Stores the type of Entity that a specific Item represents</scope_note>
</dc-type> </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> </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>