1 | package edu.illinois.library.cantaloupe.resource.iiif.v2;
|
---|
2 |
|
---|
3 | import java.io.IOException;
|
---|
4 | import java.nio.file.Files;
|
---|
5 | import java.nio.file.NoSuchFileException;
|
---|
6 | import java.nio.file.Path;
|
---|
7 | import java.util.List;
|
---|
8 |
|
---|
9 | import edu.illinois.library.cantaloupe.RestletApplication;
|
---|
10 | import edu.illinois.library.cantaloupe.cache.CacheFacade;
|
---|
11 | import edu.illinois.library.cantaloupe.config.Configuration;
|
---|
12 | import edu.illinois.library.cantaloupe.config.Key;
|
---|
13 | import edu.illinois.library.cantaloupe.image.Format;
|
---|
14 | import edu.illinois.library.cantaloupe.image.Identifier;
|
---|
15 | import edu.illinois.library.cantaloupe.image.Info;
|
---|
16 | import edu.illinois.library.cantaloupe.processor.Processor;
|
---|
17 | import edu.illinois.library.cantaloupe.processor.ProcessorFactory;
|
---|
18 | import edu.illinois.library.cantaloupe.source.Source;
|
---|
19 | import edu.illinois.library.cantaloupe.source.SourceFactory;
|
---|
20 | import edu.illinois.library.cantaloupe.resource.JSONRepresentation;
|
---|
21 | import edu.illinois.library.cantaloupe.processor.ProcessorConnector;
|
---|
22 | import org.restlet.data.MediaType;
|
---|
23 | import org.restlet.data.Preference;
|
---|
24 | import org.restlet.data.Reference;
|
---|
25 | import org.restlet.representation.EmptyRepresentation;
|
---|
26 | import org.restlet.representation.Representation;
|
---|
27 | import org.restlet.resource.Get;
|
---|
28 |
|
---|
29 | //import org.greenstone.gsdl3.IIIFServerBridge; // ****
|
---|
30 |
|
---|
31 | /**
|
---|
32 | * Handles IIIF Image API 2.x information requests.
|
---|
33 | *
|
---|
34 | * @see <a href="http://iiif.io/api/image/2.1/#information-request">Information
|
---|
35 | * Requests</a>
|
---|
36 | */
|
---|
37 | public class GSInformationResource extends InformationResource {
|
---|
38 |
|
---|
39 | /**
|
---|
40 | * Redirects {@literal /:identifier} to {@literal /:identifier/info.json},
|
---|
41 | * respecting the Servlet context root and
|
---|
42 | * {@link #PUBLIC_IDENTIFIER_HEADER} header.
|
---|
43 | */
|
---|
44 | public static class RedirectingResource extends IIIF2Resource {
|
---|
45 | @Get
|
---|
46 | public Representation doGet() {
|
---|
47 | final Reference newRef = new Reference(
|
---|
48 | getPublicRootReference() +
|
---|
49 | RestletApplication.IIIF_2_PATH + "/" +
|
---|
50 | getPublicIdentifier() +
|
---|
51 | "/info.json");
|
---|
52 | redirectSeeOther(newRef);
|
---|
53 | return new EmptyRepresentation();
|
---|
54 | }
|
---|
55 | }
|
---|
56 |
|
---|
57 | /**
|
---|
58 | * Responds to information requests.
|
---|
59 | *
|
---|
60 | * @return {@link ImageInfo} instance serialized as JSON.
|
---|
61 | */
|
---|
62 | @Get
|
---|
63 | public Representation doGet() throws Exception {
|
---|
64 | final Configuration config = Configuration.getInstance();
|
---|
65 | final Identifier identifier = getIdentifier();
|
---|
66 | final CacheFacade cacheFacade = new CacheFacade();
|
---|
67 |
|
---|
68 | // If we don't need to resolve first, and are using a cache, and the
|
---|
69 | // cache contains an info matching the request, skip all the setup and
|
---|
70 | // just return the cached info.
|
---|
71 | if (!isResolvingFirst()) {
|
---|
72 | try {
|
---|
73 | Info info = cacheFacade.getInfo(identifier);
|
---|
74 | if (info != null) {
|
---|
75 | // The source format will be null or UNKNOWN if the info was
|
---|
76 | // serialized in version < 3.4.
|
---|
77 | final Format format = info.getSourceFormat();
|
---|
78 | if (format != null && !Format.UNKNOWN.equals(format)) {
|
---|
79 | final Processor processor = new ProcessorFactory().
|
---|
80 | newProcessor(format);
|
---|
81 | commitCustomResponseHeaders();
|
---|
82 | return newRepresentation(info, processor);
|
---|
83 | }
|
---|
84 | }
|
---|
85 | } catch (IOException e) {
|
---|
86 | // Don't rethrow -- it's still possible to service the request.
|
---|
87 | getLogger().severe(e.getMessage());
|
---|
88 | }
|
---|
89 | }
|
---|
90 |
|
---|
91 | final Identifier identifier_image = IdentifierToGSAssocfile.createIdentifierImage(identifier);
|
---|
92 | final Source source = new SourceFactory().newSource(identifier_image, getDelegateProxy());
|
---|
93 |
|
---|
94 | // If we are resolving first, or if the source image is not present in
|
---|
95 | // the source cache (if enabled), check access to it in preparation for
|
---|
96 | // retrieval.
|
---|
97 | final Path sourceImage = cacheFacade.getSourceCacheFile(identifier);
|
---|
98 | if (sourceImage == null || isResolvingFirst()) {
|
---|
99 | try {
|
---|
100 | source.checkAccess();
|
---|
101 | } catch (NoSuchFileException e) { // this needs to be rethrown!
|
---|
102 | if (config.getBoolean(Key.CACHE_SERVER_PURGE_MISSING, false)) {
|
---|
103 | // If the image was not found, purge it from the cache.
|
---|
104 | cacheFacade.purgeAsync(identifier);
|
---|
105 | }
|
---|
106 | throw e;
|
---|
107 | }
|
---|
108 | }
|
---|
109 |
|
---|
110 | // Get the format of the source image.
|
---|
111 | // If we are not resolving first, and there is a hit in the source
|
---|
112 | // cache, read the format from the source-cached-file, as we will
|
---|
113 | // expect source cache access to be more efficient.
|
---|
114 | // Otherwise, read it from the source.
|
---|
115 | Format format = Format.UNKNOWN;
|
---|
116 | if (!isResolvingFirst() && sourceImage != null) {
|
---|
117 | List<edu.illinois.library.cantaloupe.image.MediaType> mediaTypes =
|
---|
118 | edu.illinois.library.cantaloupe.image.MediaType.detectMediaTypes(sourceImage);
|
---|
119 | if (!mediaTypes.isEmpty()) {
|
---|
120 | format = mediaTypes.get(0).toFormat();
|
---|
121 | }
|
---|
122 | } else {
|
---|
123 | format = source.getFormat();
|
---|
124 | }
|
---|
125 |
|
---|
126 | // Obtain an instance of the processor assigned to that format.
|
---|
127 | try (Processor processor = new ProcessorFactory().newProcessor(format)) {
|
---|
128 | // Connect it to the source.
|
---|
129 | tempFileFuture = new ProcessorConnector().connect(
|
---|
130 | source, processor, identifier, format);
|
---|
131 |
|
---|
132 | final Info info = getOrReadInfo(identifier, processor);
|
---|
133 |
|
---|
134 | commitCustomResponseHeaders();
|
---|
135 |
|
---|
136 | return newRepresentation(info, processor);
|
---|
137 | }
|
---|
138 | }
|
---|
139 |
|
---|
140 | /**
|
---|
141 | * @return Full image URI corresponding to the given identifier, respecting
|
---|
142 | * the {@literal X-Forwarded-*} and
|
---|
143 | * {@link #PUBLIC_IDENTIFIER_HEADER} reverse proxy headers.
|
---|
144 | */
|
---|
145 | private String getImageURI() {
|
---|
146 | return getPublicRootReference() + RestletApplication.IIIF_2_PATH + "/" +
|
---|
147 | getPublicIdentifier();
|
---|
148 | }
|
---|
149 |
|
---|
150 | private MediaType getNegotiatedMediaType() {
|
---|
151 | MediaType mediaType;
|
---|
152 | // If the client has requested JSON-LD, set the content type to
|
---|
153 | // that; otherwise set it to JSON.
|
---|
154 | List<Preference<MediaType>> preferences = getRequest().getClientInfo().
|
---|
155 | getAcceptedMediaTypes();
|
---|
156 | if (preferences.get(0) != null && preferences.get(0).toString().
|
---|
157 | startsWith("application/ld+json")) {
|
---|
158 | mediaType = new MediaType("application/ld+json");
|
---|
159 | } else {
|
---|
160 | mediaType = new MediaType("application/json");
|
---|
161 | }
|
---|
162 | return mediaType;
|
---|
163 | }
|
---|
164 |
|
---|
165 | private Representation newRepresentation(Info info,
|
---|
166 | Processor processor) {
|
---|
167 | final ImageInfoFactory factory = new ImageInfoFactory(
|
---|
168 | processor.getSupportedFeatures(),
|
---|
169 | processor.getSupportedIIIF2Qualities(),
|
---|
170 | processor.getAvailableOutputFormats());
|
---|
171 | factory.setDelegateProxy(getDelegateProxy());
|
---|
172 |
|
---|
173 | final ImageInfo<String, Object> imageInfo = factory.newImageInfo(
|
---|
174 | getImageURI(), info, getPageIndex());
|
---|
175 | final MediaType mediaType = getNegotiatedMediaType();
|
---|
176 |
|
---|
177 | return new JSONRepresentation(imageInfo, mediaType, () -> {
|
---|
178 | if (tempFileFuture != null) {
|
---|
179 | Path tempFile = tempFileFuture.get();
|
---|
180 | if (tempFile != null) {
|
---|
181 | Files.deleteIfExists(tempFile);
|
---|
182 | }
|
---|
183 | }
|
---|
184 | return null;
|
---|
185 | });
|
---|
186 | }
|
---|
187 |
|
---|
188 | private boolean isResolvingFirst() {
|
---|
189 | return Configuration.getInstance().
|
---|
190 | getBoolean(Key.CACHE_SERVER_RESOLVE_FIRST, true);
|
---|
191 | }
|
---|
192 |
|
---|
193 | }
|
---|