source: gs3-extensions/iiif-servlet/trunk/src/src/main/java/edu/illinois/library/cantaloupe/resource/iiif/v2/GSImageResource.java@ 32877

Last change on this file since 32877 was 32877, checked in by davidb, 5 years ago

GSImageResource added in to the mix

File size: 12.2 KB
RevLine 
[32877]1package edu.illinois.library.cantaloupe.resource.iiif.v2;
2
3import edu.illinois.library.cantaloupe.RestletApplication;
4import edu.illinois.library.cantaloupe.cache.CacheFacade;
5import edu.illinois.library.cantaloupe.config.Configuration;
6import edu.illinois.library.cantaloupe.config.Key;
7import edu.illinois.library.cantaloupe.image.Format;
8import edu.illinois.library.cantaloupe.image.Identifier;
9import edu.illinois.library.cantaloupe.image.Info;
10import edu.illinois.library.cantaloupe.image.MediaType;
11import edu.illinois.library.cantaloupe.operation.OperationList;
12import edu.illinois.library.cantaloupe.processor.Processor;
13import edu.illinois.library.cantaloupe.processor.ProcessorFactory;
14import edu.illinois.library.cantaloupe.processor.UnsupportedOutputFormatException;
15import edu.illinois.library.cantaloupe.processor.UnsupportedSourceFormatException;
16import edu.illinois.library.cantaloupe.source.Source;
17import edu.illinois.library.cantaloupe.source.SourceFactory;
18import edu.illinois.library.cantaloupe.processor.ProcessorConnector;
19import edu.illinois.library.cantaloupe.resource.CachedImageRepresentation;
20import edu.illinois.library.cantaloupe.resource.IllegalClientArgumentException;
21import edu.illinois.library.cantaloupe.resource.ImageRepresentation;
22import edu.illinois.library.cantaloupe.resource.iiif.SizeRestrictedException;
23import org.restlet.data.Disposition;
24import org.restlet.representation.Representation;
25import org.restlet.representation.StringRepresentation;
26import org.restlet.resource.Get;
27
28import java.awt.Dimension;
29import java.io.IOException;
30import java.io.InputStream;
31import java.nio.file.Files;
32import java.nio.file.NoSuchFileException;
33import java.nio.file.Path;
34import java.util.List;
35import java.util.Map;
36import java.util.Set;
37
38import org.greenstone.gsdl3.IIIFServerBridge;
39
40/**
41 * Handles IIIF Image API 2.x image requests.
42 *
43 * @see <a href="http://iiif.io/api/image/2.0/#image-request-parameters">Image
44 * Request Operations</a>
45 */
46public class GSImageResource extends IIIF2Resource {
47
48 protected Source createSource(Identifier identifier) throws Exception
49 {
50 String identifier_str = identifier.toString();
51 String[] strs = identifier_str.split(":", 3);
52 if(strs == null || strs.length < 3) {
53 System.err.println("identifier is not in the form site:coll:id" + identifier_str);
54 return null;
55 }
56 String site_name = strs[0];
57 String coll_name = strs[1];
58 String doc_id = strs[2];
59
60 // Move into Constructor ???
61 IIIFServerBridge gs_iiif_bridge = new IIIFServerBridge();
62 // and keep cache of of bridges in hashmap, keyed on sitename??
63 gs_iiif_bridge.init(site_name);
64
65 String collect_image_filename = gs_iiif_bridge.doGetDocumentMessage(coll_name + ":" + doc_id);
66 String site_image_filename = site_name + "/collect/" + coll_name + "/index/assoc/" + collect_image_filename;
67
68 System.err.println("**** Greenstone site image filename = " + site_image_filename);
69
70 final Identifier identifier_image = new Identifier(site_image_filename);
71 System.err.println("***** identifier_image = " + identifier_image);
72
73 final Source source = new SourceFactory().newSource(identifier_image, getDelegateProxy());
74
75 System.err.println("***** source path = " + ((edu.illinois.library.cantaloupe.source.FileSource)source).getPath());
76
77 return source;
78 }
79
80 /**
81 * Responds to image requests.
82 */
83 @Get
84 public Representation doGet() throws Exception {
85 final Configuration config = Configuration.getInstance();
86 final Map<String,Object> attrs = getRequest().getAttributes();
87 final Identifier identifier = getIdentifier();
88 final CacheFacade cacheFacade = new CacheFacade();
89
90 // Assemble the URI parameters into a Parameters object.
91 final Parameters params = new Parameters(
92 identifier,
93 (String) attrs.get("region"),
94 (String) attrs.get("size"),
95 (String) attrs.get("rotation"),
96 (String) attrs.get("quality"),
97 (String) attrs.get("format"));
98 final OperationList ops = params.toOperationList();
99 ops.getOptions().putAll(
100 getReference().getQueryAsForm(true).getValuesMap());
101
102 final Disposition disposition = getRepresentationDisposition(
103 getReference().getQueryAsForm()
104 .getFirstValue(RESPONSE_CONTENT_DISPOSITION_QUERY_ARG),
105 ops.getIdentifier(), ops.getOutputFormat());
106
107 Format sourceFormat = Format.UNKNOWN;
108
109 // If we don't need to resolve first, and are using a cache:
110 // 1. If the cache contains an image matching the request, skip all the
111 // setup and just return the cached image.
112 // 2. Otherwise, if the cache contains a relevant info, get it to avoid
113 // having to get it from a source later.
114 if (!isResolvingFirst()) {
115 final Info info = cacheFacade.getInfo(identifier);
116 if (info != null) {
117 ops.applyNonEndpointMutations(info, getDelegateProxy());
118
119 InputStream cacheStream = null;
120 try {
121 cacheStream = cacheFacade.newDerivativeImageInputStream(ops);
122 } catch (IOException e) {
123 // Don't rethrow -- it's still possible to service the
124 // request.
125 getLogger().severe(e.getMessage());
126 }
127
128 if (cacheStream != null) {
129 addLinkHeader(params);
130 commitCustomResponseHeaders();
131
132 return new CachedImageRepresentation(
133 cacheStream,
134 params.getOutputFormat().toFormat().getPreferredMediaType(),
135 disposition);
136 } else {
137 Format infoFormat = info.getSourceFormat();
138 if (infoFormat != null) {
139 sourceFormat = infoFormat;
140 }
141 }
142 }
143 }
144
145 final Source source = createSource(identifier);
146 //final Source source = new SourceFactory().newSource(
147 // identifier, getDelegateProxy());
148
149 // If we are resolving first, or if the source image is not present in
150 // the source cache (if enabled), check access to it in preparation for
151 // retrieval.
152 final Path sourceImage = cacheFacade.getSourceCacheFile(identifier);
153 if (sourceImage == null || isResolvingFirst()) {
154 try {
155 source.checkAccess();
156 } catch (NoSuchFileException e) { // this needs to be rethrown!
157 if (config.getBoolean(Key.CACHE_SERVER_PURGE_MISSING, false)) {
158 // If the image was not found, purge it from the cache.
159 cacheFacade.purgeAsync(ops.getIdentifier());
160 }
161 throw e;
162 }
163 }
164
165 // If we don't know the format yet, get it.
166 if (Format.UNKNOWN.equals(sourceFormat)) {
167 // If we are not resolving first, and there is a hit in the source
168 // cache, read the format from the source-cached-file, as we will
169 // expect source cache access to be more efficient.
170 // Otherwise, read it from the source.
171 if (!isResolvingFirst() && sourceImage != null) {
172 List<MediaType> mediaTypes = MediaType.detectMediaTypes(sourceImage);
173 if (!mediaTypes.isEmpty()) {
174 sourceFormat = mediaTypes.get(0).toFormat();
175 }
176 } else {
177 sourceFormat = source.getFormat();
178 }
179 }
180
181 // Obtain an instance of the processor assigned to that format. This
182 // must eventually be close()d, but we don't want to close it here
183 // unless there is an error.
184 final Processor processor = new ProcessorFactory().
185 newProcessor(sourceFormat);
186
187 try {
188 // Connect it to the source.
189 tempFileFuture = new ProcessorConnector().connect(
190 source, processor, identifier, sourceFormat);
191
192 final Info info = getOrReadInfo(ops.getIdentifier(), processor);
193 Dimension fullSize;
194 try {
195 fullSize = info.getSize(getPageIndex());
196 } catch (IndexOutOfBoundsException e) {
197 throw new IllegalClientArgumentException(e);
198 }
199
200 getRequestContext().setOperationList(ops, fullSize);
201
202 StringRepresentation redirectingRep = checkRedirect();
203 if (redirectingRep != null) {
204 return redirectingRep;
205 }
206
207 checkAuthorization();
208
209 validateRequestedArea(ops, sourceFormat, fullSize);
210
211 try {
212 processor.validate(ops, fullSize);
213 } catch (IllegalArgumentException e) {
214 throw new IllegalClientArgumentException(e.getMessage(), e);
215 }
216
217 final Dimension resultingSize = ops.getResultingSize(info.getSize());
218 validateSize(resultingSize, info.getOrientationSize(), processor);
219
220 try {
221 ops.applyNonEndpointMutations(info, getDelegateProxy());
222 } catch (IllegalStateException e) {
223 // applyNonEndpointMutations() will freeze the instance, and it
224 // may have already been called. That's fine.
225 }
226
227 // Find out whether the processor supports the source format by asking
228 // it whether it offers any output formats for it.
229 Set<Format> availableOutputFormats = processor.getAvailableOutputFormats();
230 if (!availableOutputFormats.isEmpty()) {
231 if (!availableOutputFormats.contains(ops.getOutputFormat())) {
232 Exception e = new UnsupportedOutputFormatException(
233 processor, ops.getOutputFormat());
234 getLogger().warning(e.getMessage() + ": " + getReference());
235 throw e;
236 }
237 } else {
238 throw new UnsupportedSourceFormatException(sourceFormat);
239 }
240
241 addLinkHeader(params);
242 commitCustomResponseHeaders();
243 return new ImageRepresentation(info, processor, ops, disposition,
244 isBypassingCache(), () -> {
245 if (tempFileFuture != null) {
246 Path tempFile = tempFileFuture.get();
247 if (tempFile != null) {
248 Files.deleteIfExists(tempFile);
249 }
250 }
251 return null;
252 });
253 } catch (Throwable t) {
254 processor.close();
255 throw t;
256 }
257 }
258
259 private void addLinkHeader(Parameters params) {
260 final Identifier identifier = params.getIdentifier();
261 final String paramsStr = params.toString().replaceFirst(
262 identifier.toString(), getPublicIdentifier());
263
264 getBufferedResponseHeaders().add("Link",
265 String.format("<%s%s/%s>;rel=\"canonical\"",
266 getPublicRootReference(),
267 RestletApplication.IIIF_2_PATH, paramsStr));
268 }
269
270 private void validateSize(Dimension resultingSize,
271 Dimension virtualSize,
272 Processor processor) {
273 final Configuration config = Configuration.getInstance();
274
275 if (config.getBoolean(Key.IIIF_2_RESTRICT_TO_SIZES, false)) {
276 final ImageInfoFactory factory = new ImageInfoFactory(
277 processor.getSupportedFeatures(),
278 processor.getSupportedIIIF2Qualities(),
279 processor.getAvailableOutputFormats());
280
281 final List<ImageInfo.Size> sizes = factory.getSizes(virtualSize);
282
283 boolean ok = false;
284 for (ImageInfo.Size size : sizes) {
285 if (size.width == resultingSize.width &&
286 size.height == resultingSize.height) {
287 ok = true;
288 break;
289 }
290 }
291 if (!ok) {
292 throw new SizeRestrictedException();
293 }
294 }
295 }
296
297 private boolean isResolvingFirst() {
298 return Configuration.getInstance().
299 getBoolean(Key.CACHE_SERVER_RESOLVE_FIRST, true);
300 }
301
302}
Note: See TracBrowser for help on using the repository browser.