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.viewer.resources;
20  
21  import java.io.Closeable;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.nio.channels.SeekableByteChannel;
25  import java.nio.file.Files;
26  import java.nio.file.StandardOpenOption;
27  import java.util.Optional;
28  import java.util.Spliterator;
29  import java.util.Spliterators;
30  import java.util.stream.StreamSupport;
31  
32  import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
33  import org.apache.commons.compress.archivers.zip.ZipFile;
34  import org.apache.commons.io.IOUtils;
35  import org.mycore.access.MCRAccessManager;
36  import org.mycore.common.MCRSession;
37  import org.mycore.common.MCRSessionMgr;
38  import org.mycore.common.MCRTransactionHelper;
39  import org.mycore.datamodel.niofs.MCRPath;
40  import org.mycore.frontend.MCRFrontendUtil;
41  import org.mycore.frontend.jersey.MCRStaticContent;
42  import org.mycore.frontend.servlets.MCRServlet;
43  
44  import jakarta.servlet.http.HttpServletRequest;
45  import jakarta.servlet.http.HttpServletResponse;
46  import jakarta.ws.rs.GET;
47  import jakarta.ws.rs.Path;
48  import jakarta.ws.rs.PathParam;
49  import jakarta.ws.rs.WebApplicationException;
50  import jakarta.ws.rs.core.Context;
51  import jakarta.ws.rs.core.Response;
52  import jakarta.ws.rs.core.StreamingOutput;
53  
54  @Path("/epub")
55  @MCRStaticContent // prevents session creation
56  public class MCREpubZipResource {
57  
58      public static final String EPUB_SPLIT = ".epub/";
59  
60      @Context
61      HttpServletRequest request;
62  
63      @Context
64      private HttpServletResponse response;
65  
66      private static void suppressedClose(Closeable... closeus) {
67          for (Closeable closeme : closeus) {
68              if (closeme != null) {
69                  try {
70                      closeme.close();
71                  } catch (IOException ignored) {
72                  }
73              }
74          }
75      }
76  
77      @GET
78      @Path("/{derivateID}/{epubFilePathAndPathInEpub:.+}")
79      public Response extract(
80          @PathParam("derivateID") String derivateID,
81          @PathParam("epubFilePathAndPathInEpub") String epubFileAndSubpath) {
82          java.nio.file.Path epubPath = null;
83  
84          final String[] split = epubFileAndSubpath.split(EPUB_SPLIT, 2);
85  
86          if (split.length != 2) {
87              throw new WebApplicationException("The path seems to be wrong: " + epubFileAndSubpath,
88                  Response.Status.BAD_REQUEST);
89          }
90  
91          final String epubFile = split[0] + EPUB_SPLIT.substring(0, EPUB_SPLIT.length() - 1);
92          final String pathInEpub = split[1];
93  
94          MCRSession session = null;
95          try { // creates a quick session to get the physical path to the epub from db and check rights
96              MCRSessionMgr.unlock();
97              session = MCRServlet.getSession(request);
98              MCRSessionMgr.setCurrentSession(session);
99              MCRFrontendUtil.configureSession(session, request, response);
100             if (!MCRAccessManager.checkPermission(derivateID, MCRAccessManager.PERMISSION_READ)) {
101                 throw new WebApplicationException("No rights to read " + derivateID, Response.Status.FORBIDDEN);
102             }
103             epubPath = MCRPath.getPath(derivateID, epubFile).toPhysicalPath();
104         } catch (IOException e) {
105             throw new WebApplicationException("Error while resolving physical path of " + derivateID + ":" + epubFile,
106                 e);
107         } finally { // releases the session reliable
108             try {
109                 if (session != null && MCRTransactionHelper.isTransactionActive()) {
110                     if (MCRTransactionHelper.transactionRequiresRollback()) {
111                         MCRTransactionHelper.rollbackTransaction();
112                     } else {
113                         MCRTransactionHelper.commitTransaction();
114                     }
115                 }
116             } finally {
117                 MCRSessionMgr.releaseCurrentSession();
118                 MCRSessionMgr.lock();
119             }
120         }
121 
122         if (!Files.exists(epubPath)) {
123             throw new WebApplicationException("The file " + derivateID + ":" + epubFile + " is not present!",
124                 Response.Status.NOT_FOUND);
125         }
126 
127         // try with closeable can not be used in this case.
128         // The streams/files would be closed after we leave this method
129         // StreamingOutput is called after this method is left
130         // StreamingOutput would try to call write/read on the closed stream
131         // this method is responsible for the streams and the zip file until the point responsible is set to false
132         // after that the StreamingOutput is responsible
133         SeekableByteChannel epubStream = null;
134         InputStream zipFileStream = null;
135         ZipFile zipFile = null;
136         boolean responsible = true;
137 
138         try {
139             epubStream = Files.newByteChannel(epubPath, StandardOpenOption.READ);
140             zipFile = new ZipFile(epubStream);
141 
142             final Optional<ZipArchiveEntry> entryOfFileInEpub = StreamSupport
143                 .stream(Spliterators.spliteratorUnknownSize(zipFile.getEntries().asIterator(), Spliterator.ORDERED),
144                     false)
145                 .filter(entry -> !entry.isDirectory())
146                 .filter(entry -> Optional.ofNullable(entry.getName()).filter(pathInEpub::equals).isPresent())
147                 .findFirst();
148 
149             final ZipArchiveEntry zipArchiveEntry = entryOfFileInEpub
150                 .orElseThrow(() -> new WebApplicationException("EPUB does not contain: " + pathInEpub,
151                     Response.Status.NOT_FOUND));
152 
153             zipFileStream = zipFile.getInputStream(zipArchiveEntry);
154 
155             final InputStream finalZipFileStream = zipFileStream;
156             final Closeable finalZipFile = zipFile;
157             final Closeable finalEpubStream = epubStream;
158 
159             StreamingOutput out = output -> {
160                 try {
161                     IOUtils.copy(finalZipFileStream, output);
162                 } catch (IOException e) {
163                     // suppress spamming the console with broken pipe on request abort
164                 } finally {
165                     suppressedClose(finalZipFileStream, finalZipFile, finalEpubStream);
166                 }
167             };
168             responsible = false;
169             return Response.ok(out).build();
170         } catch (IOException e) {
171             throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR);
172         } finally {
173             if (responsible) {
174                 // if responsible is true then this method is responsible for closing
175                 suppressedClose(zipFileStream, zipFile, epubStream);
176             }
177         }
178     }
179 }