diff --git a/static/plugins/uv/uv_init.js b/static/plugins/uv/uv_init.js
index 9244b3905b..e90b327ad5 100644
--- a/static/plugins/uv/uv_init.js
+++ b/static/plugins/uv/uv_init.js
@@ -5,7 +5,7 @@
try {
let iiifurlAdaptor = new UV.IIIFURLAdaptor()
let data = iiifurlAdaptor.getInitialData({
- manifest: 'jp2Proxy/' + $UV.dataset.url + '/jp2/manifest',
+ manifest: 'services/api/iiif/v3/' + $UV.dataset.url + '/manifest',
locales: [{ name: "en-GB" }]
});
let viewer = UV.init('jp2_viewer', data);
diff --git a/web-services-app/pom.xml b/web-services-app/pom.xml
index 35e44b0794..ac8b7f2410 100644
--- a/web-services-app/pom.xml
+++ b/web-services-app/pom.xml
@@ -166,6 +166,12 @@
wiremock-jre8
test
+
+ info.freelibrary
+ jiiify-presentation-v3
+
+ 0.12.3
+
jakarta.servlet
diff --git a/web-services-app/src/main/java/edu/unc/lib/boxc/web/services/processing/DownloadImageService.java b/web-services-app/src/main/java/edu/unc/lib/boxc/web/services/processing/DownloadImageService.java
index 278f94ebe5..7a00756abb 100644
--- a/web-services-app/src/main/java/edu/unc/lib/boxc/web/services/processing/DownloadImageService.java
+++ b/web-services-app/src/main/java/edu/unc/lib/boxc/web/services/processing/DownloadImageService.java
@@ -1,10 +1,10 @@
package edu.unc.lib.boxc.web.services.processing;
-import static edu.unc.lib.boxc.model.fcrepo.ids.RepositoryPaths.idToPath;
import edu.unc.lib.boxc.model.api.DatastreamType;
import edu.unc.lib.boxc.search.api.models.ContentObjectRecord;
import edu.unc.lib.boxc.search.api.models.Datastream;
+import edu.unc.lib.boxc.web.services.utils.ImageServerUtil;
import org.apache.commons.io.FilenameUtils;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
@@ -16,20 +16,21 @@
import java.net.URL;
import java.util.Objects;
+import static edu.unc.lib.boxc.web.services.utils.ImageServerUtil.FULL_SIZE;
+
/**
* Service to process access copy image downloads
* @author snluong
*/
public class DownloadImageService {
private String iiifBasePath;
- public static final String FULL_SIZE = "full";
+
public static final String INVALID_SIZE_MESSAGE = "Unable to determine size for access copy download";
/**
* Method contacts the IIIF server for the requested access copy image and returns it
* @param contentObjectRecord solr record of the file
- * @param size a string which is either "full" for full size or a pixel length like "1200"
- * @param pidString the UUID of the file
+ * @param size a string which is either "max" for full size or a pixel length like "1200"
* @return a response entity which contains headers and content of the access copy image
* @throws IOException
*/
@@ -40,7 +41,7 @@ public ResponseEntity streamImage(ContentObjectRecord conte
}
String pidString = contentObjectRecord.getPid().getId();
- String url = buildURL(pidString, size);
+ String url = ImageServerUtil.buildURL(iiifBasePath, pidString, size);
InputStream input = new URL(url).openStream();
InputStreamResource resource = new InputStreamResource(input);
String filename = getDownloadFilename(contentObjectRecord, size);
@@ -51,22 +52,7 @@ public ResponseEntity streamImage(ContentObjectRecord conte
.body(resource);
}
- /**
- * A method that builds the IIIF URL based on an assumption of full region, 0 rotation, and default quality.
- * @param id the UUID of the file
- * @param size a string which is either "full" for full size or a pixel length like "1200"
- * @return a string which is the URL to request the IIIF server for the image
- */
- private String buildURL(String id, String size) {
- var formattedSize = size;
- var hash = idToPath(id, 4, 2);
- var formattedId = hash + id + ".jp2";
- if (!Objects.equals(size, FULL_SIZE)) {
- // pixel length should be in !123,123 format
- formattedSize = "!" + size + "," + size;
- }
- return iiifBasePath + formattedId + "/full/" + formattedSize + "/0/default.jpg";
- }
+
/**
* Determines size based on original dimensions of requested file, unless requested size is full size.
diff --git a/web-services-app/src/main/java/edu/unc/lib/boxc/web/services/processing/IiifV3ManifestService.java b/web-services-app/src/main/java/edu/unc/lib/boxc/web/services/processing/IiifV3ManifestService.java
new file mode 100644
index 0000000000..fc14a5f213
--- /dev/null
+++ b/web-services-app/src/main/java/edu/unc/lib/boxc/web/services/processing/IiifV3ManifestService.java
@@ -0,0 +1,241 @@
+package edu.unc.lib.boxc.web.services.processing;
+
+import edu.unc.lib.boxc.auth.api.Permission;
+import edu.unc.lib.boxc.auth.api.models.AgentPrincipals;
+import edu.unc.lib.boxc.auth.api.services.AccessControlService;
+import edu.unc.lib.boxc.auth.api.services.DatastreamPermissionUtil;
+import edu.unc.lib.boxc.common.util.URIUtil;
+import edu.unc.lib.boxc.model.api.DatastreamType;
+import edu.unc.lib.boxc.model.api.ResourceType;
+import edu.unc.lib.boxc.model.api.exceptions.NotFoundException;
+import edu.unc.lib.boxc.model.api.ids.PID;
+import edu.unc.lib.boxc.search.api.models.ContentObjectRecord;
+import edu.unc.lib.boxc.search.api.models.Datastream;
+import edu.unc.lib.boxc.web.common.services.AccessCopiesService;
+import info.freelibrary.iiif.presentation.v3.AnnotationPage;
+import info.freelibrary.iiif.presentation.v3.Canvas;
+import info.freelibrary.iiif.presentation.v3.ImageContent;
+import info.freelibrary.iiif.presentation.v3.Manifest;
+import info.freelibrary.iiif.presentation.v3.PaintingAnnotation;
+import info.freelibrary.iiif.presentation.v3.properties.Label;
+import info.freelibrary.iiif.presentation.v3.properties.Metadata;
+import info.freelibrary.iiif.presentation.v3.properties.RequiredStatement;
+import info.freelibrary.iiif.presentation.v3.services.ImageService3;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static edu.unc.lib.boxc.model.api.DatastreamType.JP2_ACCESS_COPY;
+
+/**
+ * Service for generating iiif v3 manifests for repository object
+ * @author bbpennel
+ */
+public class IiifV3ManifestService {
+ private static final Logger log = LoggerFactory.getLogger(IiifV3ManifestService.class);
+ private AccessCopiesService accessCopiesService;
+ private AccessControlService accessControlService;
+ private String baseIiifv3Path;
+ private String baseAccessPath;
+ private String baseServicesApiPath;
+
+ /**
+ * Constructs a manifest record for the object identified by the provided PID
+ * @param pid
+ * @param agent
+ * @return
+ */
+ public Manifest buildManifest(PID pid, AgentPrincipals agent) {
+ assertHasAccess(pid, agent);
+ var contentObjs = accessCopiesService.listViewableFiles(pid, agent.getPrincipals());
+ if (contentObjs.size() == 0) {
+ throw new NotFoundException("No objects were found for inclusion in manifest for object " + pid.getId());
+ }
+ log.debug("Constructing manifest for {} containing {} items", pid.getId(), contentObjs.size());
+ ContentObjectRecord rootObj = contentObjs.get(0);
+ var manifest = new Manifest(getManifestPath(rootObj), new Label(getTitle(rootObj)));
+ manifest.setMetadata(constructMetadataSection(rootObj));
+ addAttribution(manifest, rootObj);
+
+ addCanvasItems(manifest, contentObjs);
+
+ return manifest;
+ }
+
+ private List constructMetadataSection(ContentObjectRecord rootObj) {
+ var metadataList = new ArrayList();
+ String abstractText = rootObj.getAbstractText();
+ if (abstractText != null) {
+ metadataList.add(new Metadata("description", abstractText));
+ }
+ addMultiValuedMetadataField(metadataList, "Creators", rootObj.getCreator());
+ addMultiValuedMetadataField(metadataList, "Subjects", rootObj.getSubject());
+ addMultiValuedMetadataField(metadataList, "Languages", rootObj.getLanguage());
+ metadataList.add(new Metadata("", "View full record"));
+ return metadataList;
+ }
+
+ private void addMultiValuedMetadataField(List metadataList, String fieldName, List values) {
+ if (!CollectionUtils.isEmpty(values)) {
+ metadataList.add(new Metadata(fieldName, String.join(", ", values)));
+ }
+ }
+
+ private String getTitle(ContentObjectRecord contentObj) {
+ String title = contentObj.getTitle();
+ return (title != null) ? title : "";
+ }
+
+ private void addAttribution(Manifest manifest, ContentObjectRecord rootObj) {
+ String attribution = "University of North Carolina Libraries, Digital Collections Repository";
+ String collection = rootObj.getParentCollectionName();
+ if (collection != null) {
+ attribution += " - Part of " + collection;
+ }
+ manifest.setRequiredStatement(new RequiredStatement("Attribution", attribution));
+ }
+
+ /**
+ * Add canvas items for each record in the set being processed
+ * @param manifest
+ * @param contentObjs
+ */
+ private void addCanvasItems(Manifest manifest, List contentObjs) {
+ var canvases = new ArrayList