1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.services.zipper;
20
21 import static org.mycore.access.MCRAccessManager.PERMISSION_READ;
22 import static org.mycore.common.MCRConstants.XLINK_NAMESPACE;
23
24 import java.io.ByteArrayOutputStream;
25 import java.io.FileNotFoundException;
26 import java.io.IOException;
27 import java.nio.file.FileVisitResult;
28 import java.nio.file.Files;
29 import java.nio.file.NoSuchFileException;
30 import java.nio.file.Path;
31 import java.nio.file.SimpleFileVisitor;
32 import java.nio.file.attribute.BasicFileAttributes;
33 import java.text.MessageFormat;
34 import java.util.Date;
35 import java.util.List;
36 import java.util.Locale;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
39
40 import org.apache.logging.log4j.LogManager;
41 import org.apache.logging.log4j.Logger;
42 import org.jdom2.Element;
43 import org.mycore.access.MCRAccessManager;
44 import org.mycore.common.MCRException;
45 import org.mycore.common.content.MCRContent;
46 import org.mycore.common.content.transformer.MCRContentTransformer;
47 import org.mycore.common.content.transformer.MCRParameterizedTransformer;
48 import org.mycore.common.xml.MCRLayoutService;
49 import org.mycore.common.xml.MCRXMLFunctions;
50 import org.mycore.common.xsl.MCRParameterCollector;
51 import org.mycore.datamodel.common.MCRISO8601Date;
52 import org.mycore.datamodel.common.MCRXMLMetadataManager;
53 import org.mycore.datamodel.metadata.MCRObjectID;
54 import org.mycore.datamodel.niofs.MCRPath;
55 import org.mycore.frontend.servlets.MCRServlet;
56 import org.mycore.frontend.servlets.MCRServletJob;
57
58 import jakarta.servlet.ServletOutputStream;
59 import jakarta.servlet.http.HttpServletRequest;
60 import jakarta.servlet.http.HttpServletResponse;
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77 public abstract class MCRCompressServlet<T extends AutoCloseable> extends MCRServlet {
78 private static final long serialVersionUID = 1L;
79
80 protected static String KEY_OBJECT_ID = MCRCompressServlet.class.getCanonicalName() + ".object";
81
82 protected static String KEY_PATH = MCRCompressServlet.class.getCanonicalName() + ".path";
83
84 private static Pattern PATH_INFO_PATTERN = Pattern.compile("\\A([\\w]+)/([\\w/]+)\\z");
85
86 private static Logger LOGGER = LogManager.getLogger(MCRCompressServlet.class);
87
88 @Override
89 protected void think(MCRServletJob job) throws Exception {
90 HttpServletRequest req = job.getRequest();
91
92 String paramid = getProperty(req, "id");
93 if (paramid == null) {
94 String pathInfo = req.getPathInfo();
95 if (pathInfo != null) {
96 paramid = pathInfo.substring(1);
97 }
98 }
99 if (paramid == null) {
100 job.getResponse().sendError(HttpServletResponse.SC_BAD_REQUEST, "What should I do?");
101 return;
102 }
103 Matcher ma = PATH_INFO_PATTERN.matcher(paramid);
104
105 MCRObjectID id;
106 String path;
107 try {
108 if (ma.find()) {
109 id = MCRObjectID.getInstance(ma.group(1));
110 path = ma.group(2);
111 } else {
112 id = MCRObjectID.getInstance(paramid);
113 path = null;
114 }
115 } catch (MCRException e) {
116 String objId = ma.find() ? ma.group(1) : paramid;
117 job.getResponse().sendError(HttpServletResponse.SC_BAD_REQUEST, "ID is not valid: " + objId);
118 return;
119 }
120 boolean readPermission = id.getTypeId().equals("derivate") ? MCRAccessManager
121 .checkDerivateContentPermission(id, PERMISSION_READ)
122 : MCRAccessManager.checkPermission(id, PERMISSION_READ);
123 if (!readPermission) {
124 job.getResponse().sendError(HttpServletResponse.SC_FORBIDDEN, "You may not read " + id);
125 return;
126 }
127 req.setAttribute(KEY_OBJECT_ID, id);
128 req.setAttribute(KEY_PATH, path);
129 }
130
131 @Override
132 protected void render(MCRServletJob job, Exception ex) throws Exception {
133 if (ex != null) {
134
135 throw ex;
136 }
137 if (job.getResponse().isCommitted()) {
138 return;
139 }
140 MCRObjectID id = (MCRObjectID) job.getRequest().getAttribute(KEY_OBJECT_ID);
141 String path = (String) job.getRequest().getAttribute(KEY_PATH);
142 try (ServletOutputStream sout = job.getResponse().getOutputStream()) {
143 StringBuffer requestURL = job.getRequest().getRequestURL();
144 if (job.getRequest().getQueryString() != null) {
145 requestURL.append('?').append(job.getRequest().getQueryString());
146 }
147 MCRISO8601Date mcriso8601Date = new MCRISO8601Date();
148 mcriso8601Date.setDate(new Date());
149 String comment = "Created by " + requestURL + " at " + mcriso8601Date.getISOString();
150 try (T container = createContainer(sout, comment)) {
151 job.getResponse().setContentType(getMimeType());
152 String filename = getFileName(id, path);
153 job.getResponse().addHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
154 if (id.getTypeId().equals("derivate")) {
155 sendDerivate(id, path, container);
156 } else {
157 sendObject(id, job, container);
158 }
159 disposeContainer(container);
160 }
161 }
162 }
163
164 private void sendObject(MCRObjectID id, MCRServletJob job, T container) throws Exception {
165 MCRContent content = MCRXMLMetadataManager.instance().retrieveContent(id);
166 if (content == null) {
167 throw new FileNotFoundException("Could not find object: " + id);
168 }
169 long lastModified = MCRXMLMetadataManager.instance().getLastModified(id);
170 HttpServletRequest req = job.getRequest();
171 byte[] metaDataContent = getMetaDataContent(content, req);
172 sendMetadataCompressed("metadata.xml", metaDataContent, lastModified, container);
173
174
175 List<Element> li = content.asXML().getRootElement().getChild("structure").getChild("derobjects")
176 .getChildren("derobject");
177
178 for (Element el : li) {
179 if (el.getAttributeValue("inherited").equals("0")) {
180 String ownerID = el.getAttributeValue("href", XLINK_NAMESPACE);
181 MCRObjectID derId = MCRObjectID.getInstance(ownerID);
182
183 if (MCRAccessManager.checkDerivateContentPermission(derId, PERMISSION_READ)
184 && MCRXMLFunctions.isDisplayedEnabledDerivate(ownerID)) {
185 sendDerivate(derId, null, container);
186 }
187 }
188 }
189 }
190
191 private byte[] getMetaDataContent(MCRContent content, HttpServletRequest req) throws Exception {
192
193 MCRParameterCollector parameters = new MCRParameterCollector(req);
194 if (parameters.getParameter("Style", null) == null) {
195 parameters.setParameter("Style", "compress");
196 }
197 MCRContentTransformer contentTransformer = MCRLayoutService.getContentTransformer(content.getDocType(),
198 parameters);
199 ByteArrayOutputStream out = new ByteArrayOutputStream(32 * 1024);
200 if (contentTransformer instanceof MCRParameterizedTransformer) {
201 ((MCRParameterizedTransformer) contentTransformer).transform(content, out, parameters);
202 } else {
203 contentTransformer.transform(content, out);
204 }
205 return out.toByteArray();
206 }
207
208 private void sendDerivate(MCRObjectID id, String path, T container) throws IOException {
209
210 MCRPath resolvedPath = MCRPath.getPath(id.toString(), path == null ? "/" : path);
211
212 if (!Files.exists(resolvedPath)) {
213 throw new NoSuchFileException(id.toString(), path, "Could not find path " + resolvedPath);
214 }
215
216 if (Files.isRegularFile(resolvedPath)) {
217 BasicFileAttributes attrs = Files.readAttributes(resolvedPath, BasicFileAttributes.class);
218 sendCompressedFile(resolvedPath, attrs, container);
219 LOGGER.debug("file {} zipped", resolvedPath);
220 return;
221 }
222
223 Files.walkFileTree(resolvedPath, new CompressVisitor<>(this, container));
224 }
225
226 private String getFileName(MCRObjectID id, String path) {
227 if (path == null || path.equals("")) {
228 return new MessageFormat("{0}.{1}", Locale.ROOT).format(new Object[] { id, getFileExtension() });
229 } else {
230 return new MessageFormat("{0}-{1}.{2}", Locale.ROOT).format(
231 new Object[] { id, path.replaceAll("/", "-"), getFileExtension() });
232 }
233 }
234
235
236
237
238
239 protected String getFilename(MCRPath path) {
240 return path.getNameCount() == 0 ? path.getOwner()
241 : path.getOwner() + '/'
242 + path.getRoot().relativize(path);
243 }
244
245 protected abstract void sendCompressedDirectory(MCRPath file, BasicFileAttributes attrs, T container)
246 throws IOException;
247
248 protected abstract void sendCompressedFile(MCRPath file, BasicFileAttributes attrs, T container) throws IOException;
249
250 protected abstract void sendMetadataCompressed(String fileName, byte[] content, long modified, T container)
251 throws IOException;
252
253 protected abstract String getMimeType();
254
255 protected abstract String getFileExtension();
256
257 protected abstract T createContainer(ServletOutputStream sout, String comment);
258
259 protected abstract void disposeContainer(T container) throws IOException;
260
261 private static class CompressVisitor<T extends AutoCloseable> extends SimpleFileVisitor<Path> {
262
263 private MCRCompressServlet<T> impl;
264
265 private T container;
266
267 CompressVisitor(MCRCompressServlet<T> impl, T container) {
268 this.impl = impl;
269 this.container = container;
270 }
271
272 @Override
273 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
274 impl.sendCompressedDirectory(MCRPath.toMCRPath(dir), attrs, container);
275 return super.preVisitDirectory(dir, attrs);
276 }
277
278 @Override
279 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
280 impl.sendCompressedFile(MCRPath.toMCRPath(file), attrs, container);
281 LOGGER.debug("file {} zipped", file);
282 return super.visitFile(file, attrs);
283 }
284
285 }
286
287 }