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

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

Copy refactoring

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