001/*
002 * (C) Copyright 2015-2019 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Florent Guillaume
018 *     Estelle Giuy <egiuly@nuxeo.com>
019 */
020package org.nuxeo.ecm.core.io.download;
021
022import java.io.IOException;
023import java.io.OutputStream;
024import java.io.Serializable;
025import java.util.Calendar;
026import java.util.List;
027import java.util.Map;
028import java.util.function.Consumer;
029import java.util.function.Supplier;
030
031import javax.servlet.http.HttpServletRequest;
032import javax.servlet.http.HttpServletResponse;
033
034import org.nuxeo.ecm.core.api.Blob;
035import org.nuxeo.ecm.core.api.DocumentModel;
036import org.nuxeo.ecm.core.api.impl.blob.AsyncBlob;
037import org.nuxeo.ecm.core.blob.ByteRange;
038
039/**
040 * This service allows the download of blobs to a HTTP response.
041 *
042 * @since 7.3
043 */
044public interface DownloadService {
045
046    String EVENT_NAME = "download";
047
048    String NXFILE = "nxfile";
049
050    String NXDOWNLOADINFO = "nxdownloadinfo";
051
052    /**
053     * @since 9.3
054     */
055    String NXBLOBSTATUS = "nxblobstatus";
056
057    String NXBIGBLOB = "nxbigblob";
058
059    /** @deprecated since 9.1, use nxbigblob instead */
060    @Deprecated
061    String NXBIGZIPFILE = "nxbigzipfile";
062
063    /** @deprecated since 7.4, use nxfile instead */
064    @Deprecated
065    String NXBIGFILE = "nxbigfile";
066
067    String BLOBHOLDER_PREFIX = "blobholder:";
068
069    String BLOBHOLDER_0 = "blobholder:0";
070
071    /**
072     * The transient store parameter name for storing an error if any. Stored entry must
073     */
074    String TRANSIENT_STORE_PARAM_ERROR = "error";
075
076    String TRANSIENT_STORE_PARAM_PROGRESS = "progress";
077
078    String TRANSIENT_STORE_STORE_NAME = "download";
079
080    /**
081     * The extended info containing the rendition name.
082     *
083     * @since 11.1
084     */
085    String EXTENDED_INFO_RENDITION = "rendition";
086
087    /**
088     * Internal request attribute recording the reason to use for the download.
089     *
090     * @since 11.1
091     */
092    String REQUEST_ATTR_DOWNLOAD_REASON = "nuxeo.download.reason";
093
094    /**
095     * Internal request attribute recording the rendition triggering the download.
096     *
097     * @since 11.1
098     */
099    String REQUEST_ATTR_DOWNLOAD_RENDITION = "nuxeo.download.rendition";
100
101    /**
102     * Download context.
103     *
104     * @since 11.1
105     */
106    class DownloadContext {
107
108        protected final HttpServletRequest request;
109
110        protected final HttpServletResponse response;
111
112        protected final DocumentModel doc;
113
114        protected final String xpath;
115
116        protected final Blob blob;
117
118        protected final String filename;
119
120        protected final String reason;
121
122        protected final Map<String, Serializable> extendedInfos;
123
124        protected final Boolean inline;
125
126        protected final Consumer<ByteRange> blobTransferer;
127
128        protected final Calendar lastModified;
129
130        public DownloadContext(HttpServletRequest request, HttpServletResponse response, DocumentModel doc,
131                String xpath, Blob blob, String filename, String reason, Map<String, Serializable> extendedInfos,
132                Boolean inline, Consumer<ByteRange> blobTransferer, Calendar lastModified) {
133            this.request = request;
134            this.response = response;
135            this.doc = doc;
136            this.xpath = xpath;
137            this.blob = blob;
138            this.filename = filename;
139            this.reason = reason;
140            this.extendedInfos = extendedInfos;
141            this.inline = inline;
142            this.blobTransferer = blobTransferer;
143            this.lastModified = lastModified;
144        }
145
146        public HttpServletRequest getRequest() {
147            return request;
148        }
149
150        public HttpServletResponse getResponse() {
151            return response;
152        }
153
154        public DocumentModel getDocumentModel() {
155            return doc;
156        }
157
158        public String getXPath() {
159            return xpath;
160        }
161
162        public Blob getBlob() {
163            return blob;
164        }
165
166        public String getFilename() {
167            return filename;
168        }
169
170        public String getReason() {
171            return reason;
172        }
173
174        public Map<String, Serializable> getExtendedInfos() {
175            return extendedInfos;
176        }
177
178        public Boolean getInline() {
179            return inline;
180        }
181
182        public Consumer<ByteRange> getBlobTransferer() {
183            return blobTransferer;
184        }
185
186        public Calendar getLastModified() {
187            return lastModified;
188        }
189
190        /**
191         * Creates a new builder.
192         *
193         * @param request the HTTP request
194         * @param response the HTTP response
195         * @return the new builder
196         */
197        public static Builder builder(HttpServletRequest request, HttpServletResponse response) {
198            return new Builder(request, response);
199        }
200
201        /**
202         * Builder for a {@link DownloadContext}.
203         *
204         * @since 11.1
205         */
206        public static class Builder {
207
208            protected final HttpServletRequest request;
209
210            protected final HttpServletResponse response;
211
212            protected DocumentModel doc;
213
214            protected String xpath;
215
216            protected Blob blob;
217
218            protected String filename;
219
220            protected String reason;
221
222            protected Map<String, Serializable> extendedInfos;
223
224            protected Boolean inline;
225
226            protected Consumer<ByteRange> blobTransferer;
227
228            protected Calendar lastModified;
229
230            public Builder(HttpServletRequest request, HttpServletResponse response) {
231                this.request = request;
232                this.response = response;
233            }
234
235            /**
236             * The document from which to download the blob.
237             */
238            public Builder doc(DocumentModel doc) {
239                this.doc = doc;
240                return this;
241            }
242
243            /**
244             * The blob's xpath in the document, or the blobholder index.
245             */
246            public Builder xpath(String xpath) {
247                this.xpath = xpath;
248                return this;
249            }
250
251            /**
252             * The blob, if already fetched. Otherwise, it will be retrieved from the document and the xpath.
253             */
254            public Builder blob(Blob blob) {
255                this.blob = blob;
256                return this;
257            }
258
259            /**
260             * The filename to use. If absent, the blob's filename will be used.
261             */
262            public Builder filename(String filename) {
263                this.filename = filename;
264                return this;
265            }
266
267            /**
268             * The download reason.
269             * <p>
270             * The request attribute {@link #REQUEST_ATTR_DOWNLOAD_REASON}, if present, will be used instead.
271             */
272            public Builder reason(String reason) {
273                this.reason = reason;
274                return this;
275            }
276
277            /**
278             * The extended infos, holding additional info about the download (in particular for logging and permission
279             * checks).
280             * <p>
281             * The request attribute {@link #REQUEST_ATTR_DOWNLOAD_RENDITION}, if present, will be used for the
282             * {@code rendition} key.
283             */
284            public Builder extendedInfos(Map<String, Serializable> extendedInfos) {
285                this.extendedInfos = extendedInfos;
286                return this;
287            }
288
289            /**
290             * If {@code true}, specifies {@code Content-Disposition: inline}, and if {@code false} uses
291             * {@code Content-Disposition: attachment}.
292             * <p>
293             * If {@code null}, a request parameter {@code inline} will be checked instead.
294             * <p>
295             * By default, {@code Content-Disposition: inline} will be used.
296             */
297            public Builder inline(Boolean inline) {
298                this.inline = inline;
299                return this;
300            }
301
302            /**
303             * The consumer sending the blob's bytes to a client for a given byte range.
304             * <p>
305             * The default is just to send to the response output stream, without buffering.
306             */
307            public Builder blobTransferer(Consumer<ByteRange> blobTransferer) {
308                this.blobTransferer = blobTransferer;
309                return this;
310            }
311
312            /**
313             * The last modified date of the blob.
314             */
315            public Builder lastModified(Calendar lastModified) {
316                this.lastModified = lastModified;
317                return this;
318            }
319
320            /**
321             * Builds a final {@link DownloadContext}.
322             */
323            public DownloadContext build() {
324                return new DownloadContext(request, response, doc, xpath, blob, filename, reason, extendedInfos, inline,
325                        blobTransferer, lastModified);
326            }
327        }
328    }
329
330    /**
331     * Stores the blobs for later download.
332     *
333     * @param blobs the list of blobs to store
334     * @return the store key used for retrieving the blobs (@see {@link DownloadService#getDownloadUrl(String)}
335     * @since 9.1
336     */
337    String storeBlobs(List<Blob> blobs);
338
339    /**
340     * Gets the full download URL (after redirects if configured so) for the given blob.
341     *
342     * @implSpec Configuration to follow redirects is done through the {@code org.nuxeo.download.url.follow.redirect}
343     *           configuration property.
344     * @param doc the document
345     * @param xpath the blob's xpath or blobholder index, or {@code null} for default
346     * @param blob the blob
347     * @param baseUrl the base URL to use for Nuxeo downloads (if there is no redirect)
348     * @return the full URL (which may be to a redirected server)
349     * @since 11.1
350     */
351    String getFullDownloadUrl(DocumentModel doc, String xpath, Blob blob, String baseUrl);
352
353    /**
354     * Gets the URL to use to download the blob at the given xpath in the given document.
355     * <p>
356     * The URL is relative to the Nuxeo Web Application context.
357     * <p>
358     * Returns something like {@code nxfile/reponame/docuuid/blobholder:0/foo.jpg?changeToken=5-1}
359     *
360     * @param doc the document
361     * @param xpath the blob's xpath or blobholder index, or {@code null} for default
362     * @param filename the blob's filename, or {@code null} for default
363     * @return the download URL with changeToken as query param for optimized http caching
364     */
365    String getDownloadUrl(DocumentModel doc, String xpath, String filename);
366
367    /**
368     * Gets the URL to use to download the blob at the given xpath in the given document.
369     * <p>
370     * The URL is relative to the Nuxeo Web Application context.
371     * <p>
372     * Returns something like {@code nxfile/reponame/docuuid/blobholder:0/foo.jpg?changeToken=5-1}
373     *
374     * @param repositoryName the document repository
375     * @param docId the document id
376     * @param xpath the blob's xpath or blobholder index, or {@code null} for default
377     * @param filename the blob's filename, or {@code null} for default
378     * @param changeToken the doc changeToken which will be appended as a query parameter for optimized http caching.
379     * @return the download URL
380     * @since 10.3
381     */
382    String getDownloadUrl(String repositoryName, String docId, String xpath, String filename, String changeToken);
383
384    /**
385     * Gets the URL to use to download the blob at the given xpath in the given document.
386     * <p>
387     * The URL is relative to the Nuxeo Web Application context.
388     * <p>
389     * Returns something like {@code nxfile/reponame/docuuid/blobholder:0/foo.jpg}
390     *
391     * @param repositoryName the document repository
392     * @param docId the document id
393     * @param xpath the blob's xpath or blobholder index, or {@code null} for default
394     * @param filename the blob's filename, or {@code null} for default
395     * @return the download URL
396     */
397    String getDownloadUrl(String repositoryName, String docId, String xpath, String filename);
398
399    /**
400     * Gets the URL to use to download the blobs identified by a storage key.
401     * <p>
402     * The URL is relative to the Nuxeo Web Application context.
403     * <p>
404     * Returns something like {@code nxbigblob/key}
405     *
406     * @param storeKey The key of stored blobs to download
407     * @return the download URL
408     * @since 9.1
409     */
410    String getDownloadUrl(String storeKey);
411
412    /**
413     * Finds a document's blob given the download URL returned by {@link #getDownloadUrl}.
414     * <p>
415     * The permissions are check whether the user can download the blob or not.
416     *
417     * @param downloadURL the URL to use to download the blob
418     * @return the blob, or {@code null} if not found or if the user has no permission to download it
419     * @since 9.1
420     */
421    Blob resolveBlobFromDownloadUrl(String downloadURL);
422
423    /**
424     * Handles the download of a document.
425     *
426     * @param req the request
427     * @param resp the response
428     * @param baseUrl the request baseUrl
429     * @param path the request path, without the context
430     * @since 9.1
431     */
432    void handleDownload(HttpServletRequest req, HttpServletResponse resp, String baseUrl, String path)
433            throws IOException;
434
435    /**
436     * Triggers a {@link AsyncBlob} download which gives information about an asynchronous blob.
437     *
438     * @param storeKey the stored blobs key
439     * @param reason the download reason
440     * @since 9.3
441     * @deprecated since 10.3, use the @async operation adapter instead.
442     */
443    @Deprecated
444    void downloadBlobStatus(HttpServletRequest request, HttpServletResponse response, String storeKey, String reason)
445            throws IOException;
446
447    /**
448     * Triggers a blobs download. Once the temporary blobs are transfered from the store, they are automatically
449     * deleted. The returned HTTP Status Code is 200 if the blob is ready or 202 if it is still being processed.
450     *
451     * @param storeKey the stored blobs key
452     * @param reason the download reason
453     * @since 9.1
454     */
455    void downloadBlob(HttpServletRequest request, HttpServletResponse response, String storeKey, String reason)
456            throws IOException;
457
458    /**
459     * Triggers a blob download.
460     *
461     * @param doc the document, if available
462     * @param xpath the blob's xpath or blobholder index, if available
463     * @param blob the blob, if already fetched
464     * @param filename the filename to use
465     * @param reason the download reason
466     * @deprecated since 11.1, use {@link #downloadBlob(DownloadContext)} instead
467     */
468    @Deprecated
469    void downloadBlob(HttpServletRequest request, HttpServletResponse response, DocumentModel doc, String xpath,
470            Blob blob, String filename, String reason) throws IOException;
471
472    /**
473     * Triggers a blob download.
474     *
475     * @param doc the document, if available
476     * @param xpath the blob's xpath or blobholder index, if available
477     * @param blob the blob, if already fetched
478     * @param filename the filename to use
479     * @param reason the download reason
480     * @param extendedInfos an optional map of extended informations to log
481     * @deprecated since 11.1, use {@link #downloadBlob(DownloadContext)} instead
482     */
483    @Deprecated
484    void downloadBlob(HttpServletRequest request, HttpServletResponse response, DocumentModel doc, String xpath,
485            Blob blob, String filename, String reason, Map<String, Serializable> extendedInfos) throws IOException;
486
487    /**
488     * Triggers a blob download.
489     *
490     * @param doc the document, if available
491     * @param xpath the blob's xpath or blobholder index, if available
492     * @param blob the blob, if already fetched
493     * @param filename the filename to use
494     * @param reason the download reason
495     * @param extendedInfos an optional map of extended informations to log
496     * @param inline if not null, force the inline flag for content-disposition
497     * @deprecated since 11.1, use {@link #downloadBlob(DownloadContext)} instead
498     */
499    @Deprecated
500    void downloadBlob(HttpServletRequest request, HttpServletResponse response, DocumentModel doc, String xpath,
501            Blob blob, String filename, String reason, Map<String, Serializable> extendedInfos, Boolean inline)
502            throws IOException;
503
504    /**
505     * Triggers a blob download.
506     *
507     * @param doc the document, if available
508     * @param xpath the blob's xpath or blobholder index, if available
509     * @param blob the blob, if already fetched
510     * @param filename the filename to use
511     * @param reason the download reason
512     * @param extendedInfos an optional map of extended informations to log
513     * @param inline if not null, force the inline flag for content-disposition
514     * @param blobTransferer the transferer of the actual blob
515     * @since 7.10
516     * @deprecated since 11.1, use {@link #downloadBlob(DownloadContext)} instead
517     */
518    @Deprecated
519    void downloadBlob(HttpServletRequest request, HttpServletResponse response, DocumentModel doc, String xpath,
520            Blob blob, String filename, String reason, Map<String, Serializable> extendedInfos, Boolean inline,
521            Consumer<ByteRange> blobTransferer) throws IOException;
522
523    /**
524     * Triggers a blob download.
525     *
526     * @param context the download context
527     * @since 11.1
528     */
529    void downloadBlob(DownloadContext context) throws IOException;
530
531    /**
532     * Copies the blob stream at the given byte range into the supplied {@link OutputStream}.
533     *
534     * @param blob the blob
535     * @param byteRange the byte range
536     * @param outputStreamSupplier the {@link OutputStream} supplier
537     * @since 7.10
538     */
539    void transferBlobWithByteRange(Blob blob, ByteRange byteRange, Supplier<OutputStream> outputStreamSupplier);
540
541    /**
542     * Logs a download.
543     *
544     * @param doc the doc for which this download occurs, if available
545     * @param blobXPath the blob's xpath or blobholder index, if available
546     * @param filename the filename
547     * @param reason the download reason
548     * @param extendedInfos an optional map of extended informations to log
549     * @deprecated since 11.1, use the signature including the request
550     */
551    @Deprecated
552    default void logDownload(DocumentModel doc, String blobXPath, String filename, String reason,
553            Map<String, Serializable> extendedInfos) {
554        logDownload(null, doc, blobXPath, filename, reason, extendedInfos);
555    }
556
557    /**
558     * Logs a download.
559     *
560     * @param request the HTTP request, if available
561     * @param doc the doc for which this download occurs, if available
562     * @param blobXPath the blob's xpath or blobholder index, if available
563     * @param filename the filename
564     * @param reason the download reason
565     * @param extendedInfos an optional map of extended informations to log
566     * @since 11.1
567     */
568    void logDownload(HttpServletRequest request, DocumentModel doc, String blobXPath, String filename, String reason,
569            Map<String, Serializable> extendedInfos);
570
571    /**
572     * Finds a document's blob given an xpath or blobholder index
573     *
574     * @param doc the document
575     * @param xpath the xpath or blobholder index
576     * @return the blob, or {@code null} if not found
577     */
578    Blob resolveBlob(DocumentModel doc, String xpath);
579
580    /**
581     * Finds a document's blob.
582     *
583     * @param doc the document
584     * @return the blob, or {@code null} if not available
585     * @since 9.3
586     */
587    Blob resolveBlob(DocumentModel doc);
588
589    /**
590     * Checks whether the download of the blob is allowed.
591     *
592     * @param doc the doc for which this download occurs, if available
593     * @param xpath the blob's xpath or blobholder index, if available
594     * @param blob the blob
595     * @param reason the download reason
596     * @param extendedInfos an optional map of extended informations to log
597     * @return {@code true} if download is allowed
598     * @since 7.10
599     */
600    boolean checkPermission(DocumentModel doc, String xpath, Blob blob, String reason,
601            Map<String, Serializable> extendedInfos);
602
603}