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}