mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-09-17 14:21:39 +00:00
REPO-3718: MNT-19833: Download via v1 REST API loaded in memory (#87)
(cherry picked from commitf3c3b3876f
) Cherry-picked fromf3c3b38
master to 6.0.N
This commit is contained in:
committed by
Andrei Zapodeanu
parent
0874459dcc
commit
7e5f8fe6fc
@@ -25,12 +25,13 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.repo.web.scripts;
|
package org.alfresco.repo.web.scripts;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
|
|
||||||
import org.alfresco.error.AlfrescoRuntimeException;
|
import org.alfresco.error.AlfrescoRuntimeException;
|
||||||
|
import org.apache.chemistry.opencmis.commons.server.TempStoreOutputStream;
|
||||||
|
import org.apache.chemistry.opencmis.server.shared.TempStoreOutputStreamFactory;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.extensions.surf.util.StringBuilderWriter;
|
import org.springframework.extensions.surf.util.StringBuilderWriter;
|
||||||
@@ -38,6 +39,7 @@ import org.springframework.extensions.webscripts.Cache;
|
|||||||
import org.springframework.extensions.webscripts.Runtime;
|
import org.springframework.extensions.webscripts.Runtime;
|
||||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||||
import org.springframework.extensions.webscripts.WrappingWebScriptResponse;
|
import org.springframework.extensions.webscripts.WrappingWebScriptResponse;
|
||||||
|
import org.springframework.util.FileCopyUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transactional Buffered Response
|
* Transactional Buffered Response
|
||||||
@@ -47,9 +49,10 @@ public class BufferedResponse implements WrappingWebScriptResponse
|
|||||||
// Logger
|
// Logger
|
||||||
protected static final Log logger = LogFactory.getLog(BufferedResponse.class);
|
protected static final Log logger = LogFactory.getLog(BufferedResponse.class);
|
||||||
|
|
||||||
|
private TempStoreOutputStreamFactory streamFactory;
|
||||||
private WebScriptResponse res;
|
private WebScriptResponse res;
|
||||||
private int bufferSize;
|
private int bufferSize;
|
||||||
private ByteArrayOutputStream outputStream = null;
|
private TempStoreOutputStream outputStream = null;
|
||||||
private StringBuilderWriter outputWriter = null;
|
private StringBuilderWriter outputWriter = null;
|
||||||
|
|
||||||
|
|
||||||
@@ -59,10 +62,11 @@ public class BufferedResponse implements WrappingWebScriptResponse
|
|||||||
* @param res WebScriptResponse
|
* @param res WebScriptResponse
|
||||||
* @param bufferSize int
|
* @param bufferSize int
|
||||||
*/
|
*/
|
||||||
public BufferedResponse(WebScriptResponse res, int bufferSize)
|
public BufferedResponse(WebScriptResponse res, int bufferSize, TempStoreOutputStreamFactory streamFactory)
|
||||||
{
|
{
|
||||||
this.res = res;
|
this.res = res;
|
||||||
this.bufferSize = bufferSize;
|
this.bufferSize = bufferSize;
|
||||||
|
this.streamFactory = streamFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -129,7 +133,7 @@ public class BufferedResponse implements WrappingWebScriptResponse
|
|||||||
{
|
{
|
||||||
throw new AlfrescoRuntimeException("Already buffering output writer");
|
throw new AlfrescoRuntimeException("Already buffering output writer");
|
||||||
}
|
}
|
||||||
this.outputStream = new ByteArrayOutputStream(bufferSize);
|
outputStream = streamFactory.newOutputStream();
|
||||||
}
|
}
|
||||||
return outputStream;
|
return outputStream;
|
||||||
}
|
}
|
||||||
@@ -168,7 +172,7 @@ public class BufferedResponse implements WrappingWebScriptResponse
|
|||||||
{
|
{
|
||||||
if (outputStream != null)
|
if (outputStream != null)
|
||||||
{
|
{
|
||||||
outputStream.reset();
|
outputStream = null;
|
||||||
}
|
}
|
||||||
else if (outputWriter != null)
|
else if (outputWriter != null)
|
||||||
{
|
{
|
||||||
@@ -231,7 +235,7 @@ public class BufferedResponse implements WrappingWebScriptResponse
|
|||||||
{
|
{
|
||||||
if (logger.isDebugEnabled() && outputStream != null)
|
if (logger.isDebugEnabled() && outputStream != null)
|
||||||
{
|
{
|
||||||
logger.debug("Writing Transactional response: size=" + outputStream.size());
|
logger.debug("Writing Transactional response: size=" + outputStream.getLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outputWriter != null)
|
if (outputWriter != null)
|
||||||
@@ -242,10 +246,10 @@ public class BufferedResponse implements WrappingWebScriptResponse
|
|||||||
else if (outputStream != null)
|
else if (outputStream != null)
|
||||||
{
|
{
|
||||||
if (logger.isDebugEnabled())
|
if (logger.isDebugEnabled())
|
||||||
logger.debug("Writing Transactional response: size=" + outputStream.size());
|
logger.debug("Writing Transactional response: size=" + outputStream.getLength());
|
||||||
|
|
||||||
outputStream.flush();
|
outputStream.flush();
|
||||||
outputStream.writeTo(res.getOutputStream());
|
FileCopyUtils.copy(outputStream.getInputStream(), res.getOutputStream());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
|
@@ -480,7 +480,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer
|
|||||||
|
|
||||||
// create buffered request and response that allow transaction retrying
|
// create buffered request and response that allow transaction retrying
|
||||||
bufferedReq = new BufferedRequest(scriptReq, streamFactory);
|
bufferedReq = new BufferedRequest(scriptReq, streamFactory);
|
||||||
bufferedRes = new BufferedResponse(scriptRes, trxParams.getBufferSize());
|
bufferedRes = new BufferedResponse(scriptRes, trxParams.getBufferSize(), streamFactory);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@@ -138,7 +138,7 @@ public abstract class ApiWebScript extends AbstractWebScript
|
|||||||
protected BufferedResponse getResponse(final WebScriptResponse resp)
|
protected BufferedResponse getResponse(final WebScriptResponse resp)
|
||||||
{
|
{
|
||||||
// create buffered request and response that allow transaction retrying
|
// create buffered request and response that allow transaction retrying
|
||||||
final BufferedResponse bufferedRes = new BufferedResponse(resp, memoryThreshold);
|
final BufferedResponse bufferedRes = new BufferedResponse(resp, memoryThreshold, streamFactory);
|
||||||
return bufferedRes;
|
return bufferedRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -68,6 +68,7 @@ import org.junit.runners.Suite;
|
|||||||
org.alfresco.rest.api.tests.TestUserPreferences.class,
|
org.alfresco.rest.api.tests.TestUserPreferences.class,
|
||||||
org.alfresco.rest.api.tests.WherePredicateApiTest.class,
|
org.alfresco.rest.api.tests.WherePredicateApiTest.class,
|
||||||
org.alfresco.rest.api.tests.TestRemovePermissions.class,
|
org.alfresco.rest.api.tests.TestRemovePermissions.class,
|
||||||
|
org.alfresco.rest.api.tests.BufferedResponseTest.class,
|
||||||
org.alfresco.rest.workflow.api.tests.DeploymentWorkflowApiTest.class,
|
org.alfresco.rest.workflow.api.tests.DeploymentWorkflowApiTest.class,
|
||||||
org.alfresco.rest.workflow.api.tests.ProcessDefinitionWorkflowApiTest.class,
|
org.alfresco.rest.workflow.api.tests.ProcessDefinitionWorkflowApiTest.class,
|
||||||
})
|
})
|
||||||
|
@@ -77,7 +77,8 @@ import org.junit.runners.Suite;
|
|||||||
TestPublicApi128.class,
|
TestPublicApi128.class,
|
||||||
TestPublicApiCaching.class,
|
TestPublicApiCaching.class,
|
||||||
TestDownloads.class,
|
TestDownloads.class,
|
||||||
AuditAppTest.class
|
AuditAppTest.class,
|
||||||
|
BufferedResponseTest.class
|
||||||
})
|
})
|
||||||
public class ApiTest
|
public class ApiTest
|
||||||
{
|
{
|
||||||
|
@@ -0,0 +1,116 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Remote API
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2018 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.rest.api.tests;
|
||||||
|
|
||||||
|
import org.alfresco.repo.web.scripts.BufferedResponse;
|
||||||
|
import org.alfresco.util.TempFileProvider;
|
||||||
|
import org.apache.chemistry.opencmis.server.shared.TempStoreOutputStreamFactory;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that BufferedResponse uses a temp file instead of buffering the entire output stream in memory
|
||||||
|
*
|
||||||
|
* @author Andrei Zapodeanu
|
||||||
|
* @author azapodeanu
|
||||||
|
*/
|
||||||
|
public class BufferedResponseTest
|
||||||
|
{
|
||||||
|
private static final String TEMP_FOLDER_PATH = TempFileProvider.getTempDir().getAbsolutePath();
|
||||||
|
|
||||||
|
private static final String TEMP_DIRECTORY_NAME = "testLargeFile";
|
||||||
|
private static final String LARGE_FILE_NAME = "largeFile.tmp";
|
||||||
|
private static final String FILE_PREFIX = "opencmis";
|
||||||
|
|
||||||
|
private static final Integer LARGE_FILE_SIZE_BYTES = 5 * 1024 * 1024;
|
||||||
|
private static final Integer MEMORY_THRESHOLD = 4 * 1024 * 1024;
|
||||||
|
private static final Integer MAX_CONTENT_SIZE = 1024 * 1024 * 1024;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void createSourceFile() throws IOException
|
||||||
|
{
|
||||||
|
createRandomFileInDirectory(TEMP_FOLDER_PATH, LARGE_FILE_NAME, LARGE_FILE_SIZE_BYTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception
|
||||||
|
{
|
||||||
|
File largeFileSource = new File(TEMP_FOLDER_PATH, LARGE_FILE_NAME);
|
||||||
|
largeFileSource.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that the output stream creates a temp file to cache its content when file size was bigger than its memory threshold ( 5 > 4 MB )
|
||||||
|
* MNT-19833
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testOutputStream() throws IOException
|
||||||
|
{
|
||||||
|
File bufferTempDirectory = TempFileProvider.getTempDir(TEMP_DIRECTORY_NAME);
|
||||||
|
TempStoreOutputStreamFactory streamFactory = TempStoreOutputStreamFactory.newInstance(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE,false);
|
||||||
|
BufferedResponse response = new BufferedResponse(null, 0, streamFactory);
|
||||||
|
|
||||||
|
long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX );
|
||||||
|
copyFileToOutputStream(response);
|
||||||
|
long countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX);
|
||||||
|
|
||||||
|
Assert.assertEquals(countBefore + 1, countAfter);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyFileToOutputStream(BufferedResponse response) throws IOException
|
||||||
|
{
|
||||||
|
File largeFileSource = new File(TEMP_FOLDER_PATH, LARGE_FILE_NAME);
|
||||||
|
OutputStream testOutputStream = response.getOutputStream();
|
||||||
|
Files.copy(largeFileSource.toPath(), testOutputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createRandomFileInDirectory(String path, String fileName, int size) throws IOException
|
||||||
|
{
|
||||||
|
String fullPath = new File(path, fileName).getPath();
|
||||||
|
RandomAccessFile file = new RandomAccessFile(fullPath,"rw");
|
||||||
|
file.setLength(size);
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private long countFilesInDirectoryWithPrefix(File directory, String filePrefix) throws IOException
|
||||||
|
{
|
||||||
|
Stream<File> fileStream = Arrays.stream(directory.listFiles());
|
||||||
|
return fileStream.filter( f -> f.getName().startsWith(filePrefix)).count();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Reference in New Issue
Block a user