001/* 002 * (C) Copyright 2017 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 */ 019package org.nuxeo.ecm.webengine.model.io; 020 021import java.io.IOException; 022import java.io.OutputStream; 023import java.lang.annotation.Annotation; 024import java.lang.reflect.Type; 025 026import javax.servlet.http.HttpServletRequest; 027import javax.servlet.http.HttpServletResponse; 028import javax.ws.rs.Produces; 029import javax.ws.rs.core.Context; 030import javax.ws.rs.core.MediaType; 031import javax.ws.rs.core.MultivaluedMap; 032import javax.ws.rs.ext.MessageBodyWriter; 033import javax.ws.rs.ext.Provider; 034 035import org.nuxeo.ecm.core.api.Blob; 036import org.nuxeo.ecm.core.api.DocumentModel; 037import org.nuxeo.ecm.core.api.blobholder.DocumentBlobHolder; 038import org.nuxeo.ecm.core.io.download.BufferingServletOutputStream; 039import org.nuxeo.ecm.core.io.download.DownloadHelper; 040import org.nuxeo.ecm.core.io.download.DownloadService; 041import org.nuxeo.ecm.core.io.download.DownloadService.DownloadContext; 042import org.nuxeo.runtime.api.Framework; 043import org.nuxeo.runtime.transaction.TransactionHelper; 044 045/** 046 * Writer for a {@link DocumentBlobHolder}, keeping the context of the current document, which allows for later 047 * filtering by the {@link DownloadService}. 048 * 049 * @since 9.3 050 */ 051@Provider 052@Produces({ "*/*", "text/plain" }) 053public class DocumentBlobHolderWriter implements MessageBodyWriter<DocumentBlobHolder> { 054 055 @Context 056 protected HttpServletRequest request; 057 058 @Context 059 protected HttpServletResponse response; 060 061 @Override 062 public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { 063 return DocumentBlobHolder.class.isAssignableFrom(type); 064 } 065 066 @Override 067 public long getSize(DocumentBlobHolder blobHolder, Class<?> type, Type genericType, Annotation[] annotations, 068 MediaType mediaType) { 069 long n = blobHolder.getBlob().getLength(); 070 return n < 0 ? -1 : n; 071 } 072 073 @Override 074 public void writeTo(DocumentBlobHolder blobHolder, Class<?> type, Type genericType, Annotation[] annotations, 075 MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) 076 throws IOException { 077 // ensure transaction is committed before writing blob to response 078 commitAndReopenTransaction(); 079 Blob blob = blobHolder.getBlob(); 080 // we don't want JAX-RS default headers (like Content-Type: text/plain) 081 // to be written, we control everything from the DownloadService 082 httpHeaders.clear(); 083 if (Framework.isTestModeSet()) { 084 // TODO remove this test-specific code 085 String filename = blob.getFilename(); 086 if (filename != null) { 087 String contentDisposition = DownloadHelper.getRFC2231ContentDisposition(request, filename); 088 response.setHeader("Content-Disposition", contentDisposition); 089 } 090 response.setContentType(blob.getMimeType()); 091 if (blob.getEncoding() != null) { 092 try { 093 response.setCharacterEncoding(blob.getEncoding()); 094 } catch (IllegalArgumentException e) { 095 // ignore invalid encoding 096 } 097 } 098 transferBlob(blob, entityStream); 099 return; 100 } 101 DownloadContext context = DownloadContext.builder(request, response) 102 .doc(blobHolder.getDocument()) 103 .xpath(blobHolder.getXpath()) 104 .blob(blob) 105 .reason("download") 106 .build(); 107 Framework.getService(DownloadService.class).downloadBlob(context); 108 } 109 110 protected void commitAndReopenTransaction() { 111 if (TransactionHelper.isTransactionActiveOrMarkedRollback()) { 112 TransactionHelper.commitOrRollbackTransaction(); 113 TransactionHelper.startTransaction(); 114 } 115 } 116 117 protected void transferBlob(Blob blob, OutputStream entityStream) throws IOException { 118 if (entityStream instanceof BufferingServletOutputStream) { 119 ((BufferingServletOutputStream) entityStream).stopBuffering(); 120 } 121 blob.transferTo(entityStream); 122 entityStream.flush(); 123 } 124 125}