1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.iiif.image.resources;
20
21 import static org.mycore.iiif.image.MCRIIIFImageUtil.buildCanonicalURL;
22 import static org.mycore.iiif.image.MCRIIIFImageUtil.buildProfileURL;
23 import static org.mycore.iiif.image.MCRIIIFImageUtil.completeProfile;
24 import static org.mycore.iiif.image.MCRIIIFImageUtil.encodeImageIdentifier;
25 import static org.mycore.iiif.image.MCRIIIFImageUtil.getIIIFURL;
26 import static org.mycore.iiif.image.MCRIIIFImageUtil.getImpl;
27
28 import java.awt.image.BufferedImage;
29 import java.net.URI;
30 import java.net.URISyntaxException;
31 import java.util.Date;
32 import java.util.Optional;
33 import java.util.concurrent.TimeUnit;
34
35 import javax.imageio.ImageIO;
36
37 import org.apache.logging.log4j.LogManager;
38 import org.apache.logging.log4j.Logger;
39 import org.mycore.access.MCRAccessException;
40 import org.mycore.frontend.jersey.MCRCacheControl;
41 import org.mycore.iiif.common.MCRIIIFMediaTypeHelper;
42 import org.mycore.iiif.image.impl.MCRIIIFImageImpl;
43 import org.mycore.iiif.image.impl.MCRIIIFImageNotFoundException;
44 import org.mycore.iiif.image.impl.MCRIIIFImageProvidingException;
45 import org.mycore.iiif.image.impl.MCRIIIFUnsupportedFormatException;
46 import org.mycore.iiif.image.model.MCRIIIFImageInformation;
47 import org.mycore.iiif.image.model.MCRIIIFImageProfile;
48 import org.mycore.iiif.image.model.MCRIIIFImageQuality;
49 import org.mycore.iiif.image.model.MCRIIIFImageSourceRegion;
50 import org.mycore.iiif.image.model.MCRIIIFImageTargetRotation;
51 import org.mycore.iiif.image.model.MCRIIIFImageTargetSize;
52 import org.mycore.iiif.image.parser.MCRIIIFRegionParser;
53 import org.mycore.iiif.image.parser.MCRIIIFRotationParser;
54 import org.mycore.iiif.image.parser.MCRIIIFScaleParser;
55 import org.mycore.iiif.model.MCRIIIFBase;
56
57 import com.google.gson.Gson;
58 import com.google.gson.GsonBuilder;
59
60 import jakarta.ws.rs.GET;
61 import jakarta.ws.rs.Path;
62 import jakarta.ws.rs.PathParam;
63 import jakarta.ws.rs.Produces;
64 import jakarta.ws.rs.core.Context;
65 import jakarta.ws.rs.core.Request;
66 import jakarta.ws.rs.core.Response;
67 import jakarta.ws.rs.core.StreamingOutput;
68
69 @Path("/image/v2{noop: /?}{impl: ([a-zA-Z0-9]+)?}")
70 public class MCRIIIFImageResource {
71 public static final String IIIF_IMAGE_API_2_LEVEL2 = "http://iiif.io/api/image/2/level2.json";
72
73 public static final String IMPL_PARAM = "impl";
74
75 public static final String IDENTIFIER_PARAM = "identifier";
76
77 private static final Logger LOGGER = LogManager.getLogger();
78
79 @Context
80 Request request;
81
82 Optional<Response> getCachedResponse(long lastModified) {
83 return Optional.ofNullable(request)
84 .map(r -> r.evaluatePreconditions(new Date(lastModified)))
85 .map(Response.ResponseBuilder::build);
86 }
87
88 @GET
89 @Produces(MCRIIIFMediaTypeHelper.APPLICATION_LD_JSON)
90 @Path("{" + IDENTIFIER_PARAM + "}/info.json")
91 @MCRCacheControl(maxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.DAYS),
92 sMaxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.DAYS))
93 public Response getInfo(@PathParam(IMPL_PARAM) String implString, @PathParam(IDENTIFIER_PARAM) String identifier) {
94 try {
95 MCRIIIFImageImpl impl = getImpl(implString);
96 MCRIIIFImageInformation information = impl.getInformation(identifier);
97
98 Optional<Response> cachedResponse = getCachedResponse(information.lastModified);
99 if (cachedResponse.isPresent()) {
100 return cachedResponse.get();
101 }
102
103 MCRIIIFImageProfile profile = getProfile(impl);
104
105 information.profile.add(IIIF_IMAGE_API_2_LEVEL2);
106 information.profile.add(profile);
107
108 Gson gson = new GsonBuilder().setPrettyPrinting().create();
109 return Response.ok()
110 .header("Access-Control-Allow-Origin", "*")
111 .header("Link", buildCanonicalURL(impl, identifier))
112 .header("Profile", buildProfileURL())
113 .lastModified(new Date(information.lastModified))
114 .entity(gson.toJson(information))
115 .build();
116 } catch (MCRIIIFImageNotFoundException e) {
117 return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
118 } catch (MCRAccessException e) {
119 return Response.status(Response.Status.FORBIDDEN).entity(e.getMessage()).build();
120 } catch (MCRIIIFImageProvidingException e) {
121 return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build();
122 }
123 }
124
125 @GET
126 @Path("{" + IDENTIFIER_PARAM + "}")
127 @MCRCacheControl(maxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.DAYS),
128 sMaxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.DAYS))
129 public Response getInfoRedirect(@PathParam(IMPL_PARAM) String impl,
130 @PathParam(IDENTIFIER_PARAM) String identifier) {
131 try {
132 String uriString = getIIIFURL(getImpl(impl)) + encodeImageIdentifier(identifier) + "/info.json";
133 return Response.temporaryRedirect(new URI(uriString)).build();
134 } catch (URISyntaxException e) {
135 return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
136 }
137 }
138
139 @GET
140 @Path("{" + IDENTIFIER_PARAM + "}/{region}/{size}/{rotation}/{quality}.{format}")
141 @MCRCacheControl(maxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.DAYS),
142 sMaxAge = @MCRCacheControl.Age(time = 1, unit = TimeUnit.DAYS))
143 public Response getImage(@PathParam(IMPL_PARAM) String implStr, @PathParam(IDENTIFIER_PARAM) String identifier,
144 @PathParam("region") String region,
145 @PathParam("size") String size,
146 @PathParam("rotation") String rotation,
147 @PathParam("quality") String quality,
148 @PathParam("format") String format) {
149 try {
150 MCRIIIFImageImpl impl = getImpl(implStr);
151 MCRIIIFImageInformation information = impl.getInformation(identifier);
152
153 Optional<Response> cachedResponse = getCachedResponse(information.lastModified);
154 if (cachedResponse.isPresent()) {
155 return cachedResponse.get();
156 }
157
158 MCRIIIFRegionParser rp = new MCRIIIFRegionParser(region, information.width, information.height);
159 MCRIIIFImageSourceRegion sourceRegion = rp.parseImageRegion();
160
161 MCRIIIFScaleParser sp = new MCRIIIFScaleParser(size, sourceRegion.getX2() - sourceRegion.getX1(),
162 sourceRegion.getY2() - sourceRegion.getY1());
163 MCRIIIFImageTargetSize targetSize = sp.parseTargetScale();
164
165 MCRIIIFRotationParser rotationParser = new MCRIIIFRotationParser(rotation);
166 MCRIIIFImageTargetRotation parsedRotation = rotationParser.parse();
167
168 MCRIIIFImageQuality imageQuality = MCRIIIFImageQuality.fromString(quality);
169
170 BufferedImage provide = impl
171 .provide(identifier, sourceRegion, targetSize, parsedRotation, imageQuality, format);
172
173 Response.Status status = rp.isCompleteValid() ? Response.Status.OK : Response.Status.BAD_REQUEST;
174
175 Response.ResponseBuilder responseBuilder = Response.status(status);
176 return responseBuilder
177 .header("Access-Control-Allow-Origin", "*")
178 .header("Link", buildCanonicalURL(impl, identifier))
179 .header("Profile", buildProfileURL())
180 .type("image/" + format)
181 .lastModified(new Date(information.lastModified))
182 .entity((StreamingOutput) outputStream -> ImageIO.write(provide, format, outputStream)).build();
183 } catch (MCRIIIFImageNotFoundException e) {
184 return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
185 } catch (IllegalArgumentException | MCRIIIFUnsupportedFormatException e) {
186 return Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();
187 } catch (MCRAccessException e) {
188 return Response.status(Response.Status.FORBIDDEN).entity(e.getMessage()).build();
189 } catch (Exception e) {
190 LOGGER.error(() -> "Error while getting Image " + identifier + " from " + implStr + " with region: " +
191 region + ", size: " + size + ", rotation: " + rotation + ", quality: " + quality + ", format: " +
192 format, e);
193 return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build();
194 }
195 }
196
197 @GET
198 @Path("profile.json")
199 @Produces(MCRIIIFMediaTypeHelper.APPLICATION_LD_JSON)
200 @MCRCacheControl(maxAge = @MCRCacheControl.Age(time = 7, unit = TimeUnit.DAYS),
201 sMaxAge = @MCRCacheControl.Age(time = 7, unit = TimeUnit.DAYS))
202 public Response getDereferencedProfile(@PathParam(IMPL_PARAM) String implStr) {
203 Gson gson = new GsonBuilder().setPrettyPrinting().create();
204 MCRIIIFImageProfile profile = getProfile(getImpl(implStr));
205 profile.setContext(MCRIIIFBase.API_IMAGE_2);
206 return Response.ok().entity(gson.toJson(profile)).build();
207 }
208
209 public MCRIIIFImageProfile getProfile(MCRIIIFImageImpl impl) {
210 MCRIIIFImageProfile profile = impl.getProfile();
211 completeProfile(impl, profile);
212 return profile;
213 }
214
215 }