View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
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 }